Zpracování asynchronních úloh při jejich dokončení (C#)

Pomocí příkazu Task.WhenAnymůžete spustit několik úkolů současně a zpracovávat je jeden po druhém, jakmile jsou dokončené, místo toho, abyste je zpracovávali v pořadí, ve kterém byly spuštěny.

Následující příklad používá dotaz k vytvoření kolekce úloh. Každý úkol stáhne obsah zadaného webu. V každé iteraci smyčky while očekávané volání WhenAny vrátí úlohu v kolekci úloh, která dokončí stahování jako první. Tato úloha se odebere z kolekce a zpracuje se. Smyčka se opakuje, dokud kolekce neobsahuje žádné další úlohy.

Požadavky

V tomto kurzu můžete využít jednu z následujících možností:

Vytvoření ukázkové aplikace

Vytvořte novou konzolovou aplikaci .NET Core. Můžete ho vytvořit pomocí příkazu konzoly dotnet new nebo ze sady Visual Studio.

V editoru kódu otevřete soubor Program.cs a nahraďte stávající kód tímto kódem:

using System.Diagnostics;

namespace ProcessTasksAsTheyFinish;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");
    }
}

Přidání polí

Program Do definice třídy přidejte následující dvě pole:

static readonly HttpClient s_client = new HttpClient
{
    MaxResponseContentBufferSize = 1_000_000
};

static readonly IEnumerable<string> s_urlList = new string[]
{
    "https://learn.microsoft.com",
    "https://learn.microsoft.com/aspnet/core",
    "https://learn.microsoft.com/azure",
    "https://learn.microsoft.com/azure/devops",
    "https://learn.microsoft.com/dotnet",
    "https://learn.microsoft.com/dynamics365",
    "https://learn.microsoft.com/education",
    "https://learn.microsoft.com/enterprise-mobility-security",
    "https://learn.microsoft.com/gaming",
    "https://learn.microsoft.com/graph",
    "https://learn.microsoft.com/microsoft-365",
    "https://learn.microsoft.com/office",
    "https://learn.microsoft.com/powershell",
    "https://learn.microsoft.com/sql",
    "https://learn.microsoft.com/surface",
    "https://learn.microsoft.com/system-center",
    "https://learn.microsoft.com/visualstudio",
    "https://learn.microsoft.com/windows",
    "https://learn.microsoft.com/maui"
};

Zpřístupňuje HttpClient možnost odesílat požadavky HTTP a přijímat odpovědi HTTP. Obsahuje s_urlList všechny adresy URL, které aplikace plánuje zpracovat.

Aktualizace vstupního bodu aplikace

Hlavním vstupním bodem do konzolové aplikace je Main metoda . Nahraďte existující metodu následujícím kódem:

static Task Main() => SumPageSizesAsync();

Aktualizovaná Main metoda je nyní považována za Async main, která umožňuje asynchronní vstupní bod do spustitelného souboru. Je vyjádřena voláním SumPageSizesAsyncmetody .

Vytvoření metody asynchronního součtu velikostí stránek

Pod metodu Main přidejte metodu SumPageSizesAsync :

static async Task SumPageSizesAsync()
{
    var stopwatch = Stopwatch.StartNew();

    IEnumerable<Task<int>> downloadTasksQuery =
        from url in s_urlList
        select ProcessUrlAsync(url, s_client);

    List<Task<int>> downloadTasks = downloadTasksQuery.ToList();

    int total = 0;
    while (downloadTasks.Any())
    {
        Task<int> finishedTask = await Task.WhenAny(downloadTasks);
        downloadTasks.Remove(finishedTask);
        total += await finishedTask;
    }

    stopwatch.Stop();

    Console.WriteLine($"\nTotal bytes returned:  {total:#,#}");
    Console.WriteLine($"Elapsed time:          {stopwatch.Elapsed}\n");
}

Smyčka while odebere jednu z úloh v každé iteraci. Po dokončení každého úkolu smyčka skončí. Metoda začíná vytvořením instance a spuštěním Stopwatch. Potom obsahuje dotaz, který při spuštění vytvoří kolekci úloh. Každé volání ProcessUrlAsync v následujícím kódu vrátí , Task<TResult>kde TResult je celé číslo:

IEnumerable<Task<int>> downloadTasksQuery =
    from url in s_urlList
    select ProcessUrlAsync(url, s_client);

Z důvodu odloženého spuštění s LINQ zavoláte Enumerable.ToList ke spuštění každého úkolu.

List<Task<int>> downloadTasks = downloadTasksQuery.ToList();

Smyčka while provede následující kroky pro každou úlohu v kolekci:

  1. Čeká na WhenAny volání k identifikaci první úlohy v kolekci, která dokončila stahování.

    Task<int> finishedTask = await Task.WhenAny(downloadTasks);
    
  2. Odebere tento úkol z kolekce.

    downloadTasks.Remove(finishedTask);
    
  3. Awaits finishedTask, která je vrácena voláním metody ProcessUrlAsync. Proměnná finishedTask je kde Task<TResult>TResult je celé číslo. Úkol je již dokončen, ale čekáte na načtení délky staženého webu, jak ukazuje následující příklad. Pokud je úloha chybná, await vyvolá první podřízenou výjimku uloženou v objektu AggregateException, na rozdíl od čtení Task<TResult>.Result vlastnosti, která by vyvolala AggregateExceptionvýjimku .

    total += await finishedTask;
    

Přidání metody procesu

Pod metodu přidejte následující ProcessUrlAsync metodu SumPageSizesAsync :

static async Task<int> ProcessUrlAsync(string url, HttpClient client)
{
    byte[] content = await client.GetByteArrayAsync(url);
    Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

    return content.Length;
}

Pro každou danou adresu URL metoda použije zadanou clientbyte[]instanci k získání odpovědi jako . Délka je vrácena po adrese URL a délka je zapsána do konzoly.

Spusťte program několikrát, abyste ověřili, že se stažené délky nezobrazují vždy ve stejném pořadí.

Upozornění

Pomocí smyčky in, jak je popsáno v příkladu, můžete WhenAny vyřešit problémy, které zahrnují malý počet úloh. Jiné přístupy jsou ale efektivnější, pokud potřebujete zpracovat velký počet úkolů. Další informace a příklady najdete v tématu Zpracování úloh při jejich dokončení.

Kompletní příklad

Následující kód je úplný text souboru Program.cs pro příklad.

using System.Diagnostics;

HttpClient s_client = new()
{
    MaxResponseContentBufferSize = 1_000_000
};

IEnumerable<string> s_urlList = new string[]
{
    "https://learn.microsoft.com",
    "https://learn.microsoft.com/aspnet/core",
    "https://learn.microsoft.com/azure",
    "https://learn.microsoft.com/azure/devops",
    "https://learn.microsoft.com/dotnet",
    "https://learn.microsoft.com/dynamics365",
    "https://learn.microsoft.com/education",
    "https://learn.microsoft.com/enterprise-mobility-security",
    "https://learn.microsoft.com/gaming",
    "https://learn.microsoft.com/graph",
    "https://learn.microsoft.com/microsoft-365",
    "https://learn.microsoft.com/office",
    "https://learn.microsoft.com/powershell",
    "https://learn.microsoft.com/sql",
    "https://learn.microsoft.com/surface",
    "https://learn.microsoft.com/system-center",
    "https://learn.microsoft.com/visualstudio",
    "https://learn.microsoft.com/windows",
    "https://learn.microsoft.com/maui"
};

await SumPageSizesAsync();

async Task SumPageSizesAsync()
{
    var stopwatch = Stopwatch.StartNew();

    IEnumerable<Task<int>> downloadTasksQuery =
        from url in s_urlList
        select ProcessUrlAsync(url, s_client);

    List<Task<int>> downloadTasks = downloadTasksQuery.ToList();

    int total = 0;
    while (downloadTasks.Any())
    {
        Task<int> finishedTask = await Task.WhenAny(downloadTasks);
        downloadTasks.Remove(finishedTask);
        total += await finishedTask;
    }

    stopwatch.Stop();

    Console.WriteLine($"\nTotal bytes returned:    {total:#,#}");
    Console.WriteLine($"Elapsed time:              {stopwatch.Elapsed}\n");
}

static async Task<int> ProcessUrlAsync(string url, HttpClient client)
{
    byte[] content = await client.GetByteArrayAsync(url);
    Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

    return content.Length;
}

// Example output:
// https://learn.microsoft.com                                      132,517
// https://learn.microsoft.com/powershell                            57,375
// https://learn.microsoft.com/gaming                                33,549
// https://learn.microsoft.com/aspnet/core                           88,714
// https://learn.microsoft.com/surface                               39,840
// https://learn.microsoft.com/enterprise-mobility-security          30,903
// https://learn.microsoft.com/microsoft-365                         67,867
// https://learn.microsoft.com/windows                               26,816
// https://learn.microsoft.com/maui                               57,958
// https://learn.microsoft.com/dotnet                                78,706
// https://learn.microsoft.com/graph                                 48,277
// https://learn.microsoft.com/dynamics365                           49,042
// https://learn.microsoft.com/office                                67,867
// https://learn.microsoft.com/system-center                         42,887
// https://learn.microsoft.com/education                             38,636
// https://learn.microsoft.com/azure                                421,663
// https://learn.microsoft.com/visualstudio                          30,925
// https://learn.microsoft.com/sql                                   54,608
// https://learn.microsoft.com/azure/devops                          86,034

// Total bytes returned:    1,454,184
// Elapsed time:            00:00:01.1290403

Viz také