Bearbeta asynkrona uppgifter när de slutförs (C#)

Genom att använda Task.WhenAnykan du starta flera aktiviteter samtidigt och bearbeta dem en i taget när de har slutförts i stället för att bearbeta dem i den ordning de startas.

I följande exempel används en fråga för att skapa en samling uppgifter. Varje uppgift laddar ned innehållet på en angiven webbplats. I varje iteration av en while-loop returnerar ett invänt anrop till WhenAny uppgiften i samlingen med aktiviteter som slutför nedladdningen först. Uppgiften tas bort från samlingen och bearbetas. Loopen upprepas tills samlingen inte innehåller fler uppgifter.

Förutsättningar

Du kan följa den här självstudien med något av följande alternativ:

  • Visual Studio 2022 med arbetsbelastningen för .NET-skrivbordsutveckling installerad. .NET SDK installeras automatiskt när du väljer den här arbetsbelastningen.
  • .NET SDK med valfri kodredigerare, till exempel Visual Studio Code.

Skapa exempelprogram

Skapa ett nytt .NET Core-konsolprogram. Du kan skapa ett med hjälp av det nya konsolkommandot för dotnet eller från Visual Studio.

Öppna filen Program.cs i kodredigeraren och ersätt den befintliga koden med den här koden:

using System.Diagnostics;

namespace ProcessTasksAsTheyFinish;

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

Lägg till fält

Lägg till följande två fält i Program klassdefinitionen:

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

HttpClient visar möjligheten att skicka HTTP-begäranden och ta emot HTTP-svar. s_urlList innehåller alla URL:er som programmet planerar att bearbeta.

Uppdatera programmets startpunkt

Den huvudsakliga startpunkten i konsolprogrammet är Main metoden . Ersätt den befintliga metoden med följande:

static Task Main() => SumPageSizesAsync();

Den uppdaterade Main metoden betraktas nu som en Async-huvud, vilket möjliggör en asynkron startpunkt i den körbara filen. Det uttrycks som ett anrop till SumPageSizesAsync.

Skapa metoden för sidstorlekar för asynkron summa

Main Under metoden lägger du till SumPageSizesAsync metoden :

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

Loopen while tar bort en av uppgifterna i varje iteration. När varje uppgift har slutförts avslutas loopen. Metoden börjar med att instansiera och starta en Stopwatch. Den innehåller sedan en fråga som skapar en samling uppgifter när den körs. Varje anrop till ProcessUrlAsync i följande kod returnerar ett Task<TResult>, där TResult är ett heltal:

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

På grund av uppskjuten körning med LINQ anropar Enumerable.ToList du för att starta varje uppgift.

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

Loopen while utför följande steg för varje uppgift i samlingen:

  1. Väntar på ett anrop till för att WhenAny identifiera den första uppgiften i samlingen som har slutfört nedladdningen.

    Task<int> finishedTask = await Task.WhenAny(downloadTasks);
    
  2. Tar bort uppgiften från samlingen.

    downloadTasks.Remove(finishedTask);
    
  3. Awaits finishedTask, som returneras av ett anrop till ProcessUrlAsync. Variabeln finishedTask är ett Task<TResult> där TResult är ett heltal. Uppgiften är redan slutförd, men du väntar på att den ska hämta längden på den nedladdade webbplatsen, vilket visas i följande exempel. Om uppgiften är await felaktig genererar det första underordnade undantaget som lagras i AggregateException, till skillnad från att läsa Task<TResult>.Result egenskapen , vilket skulle utlösa AggregateException.

    total += await finishedTask;
    

Lägg till processmetod

Lägg till följande ProcessUrlAsync metod under SumPageSizesAsync metoden :

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

För en viss URL använder metoden den angivna instansen client för att hämta svaret som en byte[]. Längden returneras när URL:en och längden har skrivits till konsolen.

Kör programmet flera gånger för att kontrollera att de nedladdade längderna inte alltid visas i samma ordning.

Varning

Du kan använda WhenAny i en loop, enligt beskrivningen i exemplet, för att lösa problem som omfattar ett litet antal uppgifter. Andra metoder är dock mer effektiva om du har ett stort antal uppgifter att bearbeta. Mer information och exempel finns i Bearbeta uppgifter när de slutförs.

Fullständigt exempel

Följande kod är den fullständiga texten i filen Program.cs för exemplet.

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

Se även