Delen via


Asynchrone taken verwerken wanneer ze zijn voltooid (C#)

Met behulp van Task.WhenAnykunt u meerdere taken tegelijk starten en ze één voor één verwerken terwijl ze zijn voltooid in plaats van ze te verwerken in de volgorde waarin ze worden gestart.

In het volgende voorbeeld wordt een query gebruikt om een verzameling taken te maken. Elke taak downloadt de inhoud van een opgegeven website. In elke iteratie van een tijdjelus retourneert een wachtende aanroep om de taak te WhenAny retourneren in de verzameling taken die het downloaden van de taak als eerste hebben voltooid. Deze taak wordt verwijderd uit de verzameling en verwerkt. De lus wordt herhaald totdat de verzameling geen taken meer bevat.

Vereiste voorwaarden

U kunt deze zelfstudie volgen met een van de volgende opties:

  • Visual Studio 2022 met de .NET-desktopontwikkeling workload geïnstalleerd. De .NET SDK wordt automatisch geïnstalleerd wanneer u deze workload selecteert.
  • De .NET SDK met een code-editor van uw keuze, zoals Visual Studio Code.

Voorbeeldtoepassing maken

Maak een nieuwe .NET Core-consoletoepassing. U kunt er een maken met behulp van de nieuwe dotnet-consoleopdracht of vanuit Visual Studio.

Open het Program.cs-bestand in de code-editor en vervang de bestaande code door deze code:

using System.Diagnostics;

namespace ProcessTasksAsTheyFinish;

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

Velden toevoegen

Voeg in de Program klassedefinitie de volgende twee velden toe:

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

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

De HttpClient mogelijkheid om HTTP-aanvragen te verzenden en HTTP-antwoorden te ontvangen. De s_urlList bevat alle URL's die door de toepassing moeten worden verwerkt.

Toepassingsinvoerpunt bijwerken

Het belangrijkste toegangspunt in de consoletoepassing is de Main methode. Vervang de bestaande methode door het volgende:

static Task Main() => SumPageSizesAsync();

De bijgewerkte Main methode wordt nu beschouwd als een Async-hoofd, waardoor een asynchroon toegangspunt in het uitvoerbare bestand mogelijk is. Het wordt uitgedrukt als een oproep naar SumPageSizesAsync.

De methode asynchrone som van paginaformaten maken

Voeg onder de Main methode de SumPageSizesAsync methode toe:

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");
}

Met while de lus wordt een van de taken in elke iteratie verwijderd. Nadat elke taak is voltooid, eindigt de lus. De methode begint met het instantiëren en starten van een Stopwatch. Het bevat vervolgens een query die, wanneer deze wordt uitgevoerd, een verzameling taken maakt. Elke aanroep naar ProcessUrlAsync in de volgende code retourneert een Task<TResult>, waarbij TResult een geheel getal is:

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

Als gevolg van de uitgestelde uitvoering met de LINQ roept Enumerable.ToList u aan om elke taak te starten.

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

De while lus voert de volgende stappen uit voor elke taak in de verzameling:

  1. Wacht op een aanroep om de eerste taak in de verzameling te WhenAny identificeren die de download heeft voltooid.

    Task<int> finishedTask = await Task.WhenAny(downloadTasks);
    
  2. Hiermee verwijdert u die taak uit de verzameling.

    downloadTasks.Remove(finishedTask);
    
  3. Wacht op finishedTask, die wordt geretourneerd door een oproep naar ProcessUrlAsync. De finishedTask variabele is een Task<TResult> geheel TResult getal. De taak is al voltooid, maar u wacht erop om de lengte van de gedownloade website op te halen, zoals in het volgende voorbeeld wordt weergegeven. Als er een fout optreedt bij de taak, await wordt de eerste onderliggende uitzondering gegenereerd die is opgeslagen in de AggregateExceptioneigenschap, in tegenstelling tot het lezen van de Task<TResult>.Result eigenschap, waardoor de AggregateException.

    total += await finishedTask;
    

Procesmethode toevoegen

Voeg de volgende ProcessUrlAsync methode toe onder de SumPageSizesAsync methode:

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;
}

Voor een bepaalde URL gebruikt de methode het client exemplaar dat is opgegeven om het antwoord op te halen als een byte[]. De lengte wordt geretourneerd nadat de URL en lengte naar de console is geschreven.

Voer het programma meerdere keren uit om te controleren of de gedownloade lengten niet altijd in dezelfde volgorde worden weergegeven.

Waarschuwing

U kunt in een lus, zoals beschreven in het voorbeeld, gebruiken WhenAny om problemen op te lossen die betrekking hebben op een klein aantal taken. Andere benaderingen zijn echter efficiënter als u een groot aantal taken moet verwerken. Zie Taken verwerken terwijl ze zijn voltooid voor meer informatie en voorbeelden.

De benadering vereenvoudigen met behulp van Task.WhenEach

De while lus die in SumPageSizesAsync de methode is geïmplementeerd, kan worden vereenvoudigd met behulp van de nieuwe Task.WhenEach methode die is geïntroduceerd in .NET 9 door deze in await foreach lus aan te roepen.
Vervang de eerder geïmplementeerde while lus:

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

met de vereenvoudigde await foreach:

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

Met deze nieuwe aanpak kunt u niet langer herhaaldelijk een taak aanroepen Task.WhenAny en de taak verwijderen die is voltooid, omdat Task.WhenEach taken in een volgorde van voltooiing worden herhaald.

Volledig voorbeeld

De volgende code is de volledige tekst van het Program.cs-bestand voor het voorbeeld.

using System.Diagnostics;

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

IEnumerable<string> s_urlList = new string[]
{
    "https://free.blessedness.top",
    "https://free.blessedness.top/aspnet/core",
    "https://free.blessedness.top/azure",
    "https://free.blessedness.top/azure/devops",
    "https://free.blessedness.top/dotnet",
    "https://free.blessedness.top/dynamics365",
    "https://free.blessedness.top/education",
    "https://free.blessedness.top/enterprise-mobility-security",
    "https://free.blessedness.top/gaming",
    "https://free.blessedness.top/graph",
    "https://free.blessedness.top/microsoft-365",
    "https://free.blessedness.top/office",
    "https://free.blessedness.top/powershell",
    "https://free.blessedness.top/sql",
    "https://free.blessedness.top/surface",
    "https://free.blessedness.top/system-center",
    "https://free.blessedness.top/visualstudio",
    "https://free.blessedness.top/windows",
    "https://free.blessedness.top/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://free.blessedness.top                                      132,517
// https://free.blessedness.top/powershell                            57,375
// https://free.blessedness.top/gaming                                33,549
// https://free.blessedness.top/aspnet/core                           88,714
// https://free.blessedness.top/surface                               39,840
// https://free.blessedness.top/enterprise-mobility-security          30,903
// https://free.blessedness.top/microsoft-365                         67,867
// https://free.blessedness.top/windows                               26,816
// https://free.blessedness.top/maui                               57,958
// https://free.blessedness.top/dotnet                                78,706
// https://free.blessedness.top/graph                                 48,277
// https://free.blessedness.top/dynamics365                           49,042
// https://free.blessedness.top/office                                67,867
// https://free.blessedness.top/system-center                         42,887
// https://free.blessedness.top/education                             38,636
// https://free.blessedness.top/azure                                421,663
// https://free.blessedness.top/visualstudio                          30,925
// https://free.blessedness.top/sql                                   54,608
// https://free.blessedness.top/azure/devops                          86,034

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

Zie ook