Dela via


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

Med hjälp Task.WhenAnyav kan du starta flera uppgifter 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äntat anrop för att WhenAny returnera uppgiften i samlingen med uppgifter som slutför nedladdningen först. Den uppgiften tas bort från samlingen och bearbetas. Loopen upprepas tills samlingen inte innehåller några 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 .NET desktop-utvecklingsarbetsflödet installerat. .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 dotnet-konsolkommandot 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

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

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

Gör HttpClient det möjligt att skicka HTTP-begäranden och ta emot HTTP-svar. Innehåller s_urlList alla URL:er som programmet planerar att bearbeta.

Uppdatera programmets startpunkt

Den viktigaste 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 asynkrona sum-sidstorlekar

Main Lägg till SumPageSizesAsync metoden under 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, när den körs, skapar en samling uppgifter. 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 en Task<TResult> var TResult är ett heltal. Uppgiften är redan klar, 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 felaktig await utlöser 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.

Försiktighet

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.

Förenkla metoden med hjälp av Task.WhenEach

Loopen while som implementeras i SumPageSizesAsync metoden kan förenklas med den nya Task.WhenEach metoden som introducerades i .NET 9 genom att anropa den i await foreach loop.
Ersätt den tidigare implementerade while loopen:

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

med den förenklade await foreach:

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

Med den här nya metoden kan du inte längre anropa Task.WhenAny flera gånger för att manuellt anropa en uppgift och ta bort den som har slutförts, eftersom Task.WhenEach den itererar genom uppgiften i rätt ordning.

Fullständigt exempel

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

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

Se även