Sdílet prostřednictvím


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

Pomocí nich Task.WhenAnymůžete současně spouštět více úkolů a zpracovávat je po jednom, protože jsou dokončené, a ne je zpracovávat v pořadí, ve kterém jsou spuštěné.

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 vrátí očekávané volání WhenAny úkolu v kolekci úkolů, které dokončí její stahování jako první. Tato úloha se odebere z kolekce a zpracuje se. Smyčka se opakuje, dokud kolekce neobsahuje žádné další úkoly.

Požadavky

Můžete postupovat podle tohoto kurzu pomocí jedné z následujících možností:

  • Visual Studio 2022 s nainstalovanou pracovní sadou pro vývoj desktopových aplikací .NET. Sada .NET SDK se automaticky nainstaluje při výběru této úlohy.
  • Sada .NET SDK s editorem kódu podle vašeho výběru, jako je Visual Studio Code.

Vytvoření ukázkové aplikace

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

Otevřete soubor Program.cs v editoru kódu 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 schopnost 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 asynchronní hlavní, což umožňuje asynchronní vstupní bod do spustitelného souboru. Vyjadřuje se jako volání SumPageSizesAsync.

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

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 se smyčka ukončí. Metoda začíná vytvořením instance a spuštěním Stopwatch. Pak 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>hodnotu , kde TResult je celé číslo:

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

Vzhledem k odložené provádění pomocí LINQ voláte Enumerable.ToList , aby se spustily jednotlivé úlohy.

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

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

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

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

    downloadTasks.Remove(finishedTask);
    
  3. Awaits finishedTask, který je vrácen voláním ProcessUrlAsync. Proměnná finishedTask je celé Task<TResult>TResult číslo. Úkol je již dokončený, ale očekáváte, že načte délku 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 vyvolá AggregateException.

    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 client instanci poskytnutou k získání odpovědi jako .byte[] Délka se vrátí za adresou URL a délkou se zapíše do konzoly.

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

Upozornění

Ve smyčce, jak je popsáno v příkladu, můžete použít WhenAny k řešení problémů, které zahrnují malý počet úkolů. Pokud ale máte velký počet úkolů ke zpracování, jsou další přístupy efektivnější. Další informace a příklady najdete v tématu Zpracování úloh při jejich dokončení.

Zjednodušení přístupu pomocí Task.WhenEach

Smyčku while implementovanou v SumPageSizesAsync metodě lze zjednodušit voláním ve smyčce pomocí nové Task.WhenEach metody zavedené v await foreach .NET 9.
Nahraďte dříve implementovanou while smyčku:

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

zjednodušeným await foreach:

    await foreach (Task<int> t in Task.WhenEach(downloadTasks))
    {
        total += await t;
    }

Tento nový přístup umožňuje už opakovaně volat Task.WhenAny úkol ručně a odebrat úkol, který se dokončí, protože Task.WhenEach iteruje úkol v pořadí jeho dokončení.

Kompletní příklad

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

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é