Asynchrone taken verwerken tijdens het voltooien (C#)

Met behulp van Task.WhenAnykunt u meerdere taken tegelijk starten en ze één voor één verwerken wanneer ze worden 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 herhaling van een while-lus retourneert een wachtende aanroep om WhenAny de taak te retourneren in de verzameling taken die het downloaden als eerste is voltooid. Deze taak wordt verwijderd uit de verzameling en verwerkt. De lus wordt herhaald totdat de verzameling geen taken meer bevat.

Vereisten

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

  • Visual Studio 2022 met de workload .NET Desktop Development 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 opdracht dotnet new console of vanuit Visual Studio.

Open het bestand Program.cs 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://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"
};

De HttpClient biedt de mogelijkheid om HTTP-aanvragen te verzenden en HTTP-antwoorden te ontvangen. De s_urlList bevat alle URL's die de toepassing wil verwerken.

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 Asynchrone hoofd, waarmee een asynchroon toegangspunt in het uitvoerbare bestand kan worden gemaakt. Het wordt uitgedrukt als een aanroep van SumPageSizesAsync.

De asynchrone methode voor paginaformaten voor som maken

Voeg onder de Main methode de methode toe 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");
}

De while lus verwijdert een van de taken in elke iteratie. Nadat elke taak is voltooid, wordt de lus beëindigd. 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);

Vanwege de uitgestelde uitvoering met de LINQ roept u Enumerable.ToList 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 WhenAny de eerste taak in de verzameling te identificeren die het downloaden heeft voltooid.

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

    downloadTasks.Remove(finishedTask);
    
  3. finishedTaskWacht op , dat wordt geretourneerd door een aanroep naar ProcessUrlAsync. De finishedTask variabele is een Task<TResult> waarbij TResult een geheel getal is. 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 de taak een fout heeft, await genereert de eerste onderliggende uitzondering die is opgeslagen in de AggregateException, in tegenstelling tot het lezen van de Task<TResult>.Result eigenschap, waardoor de AggregateExceptionwordt gegenereerd.

    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 opgegeven exemplaar om het antwoord op te halen als een byte[]. De lengte wordt geretourneerd nadat de URL en de 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 gebruiken WhenAny , zoals beschreven in het voorbeeld, 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 wanneer ze zijn voltooid voor meer informatie en voorbeelden.

Volledig voorbeeld

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

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

Zie ook