Aszinkron feladatok feldolgozása, ahogy befejeződnek (C#)

A használatával Task.WhenAnyegyszerre több tevékenységet is elindíthat, és egyenként feldolgozhatja őket a befejezésükkor, ahelyett, hogy a kezdési sorrendben dolgozzák fel őket.

Az alábbi példa egy lekérdezés használatával hoz létre feladatgyűjteményt. Minden feladat letölti egy adott webhely tartalmát. Egy while ciklus minden iterációjában egy várt hívás a WhenAny függvényre visszaadja a feladatot a feladatok gyűjteményéből, amelyik elsőként fejezi be a letöltését. A feladat eltávolításra kerül a gyűjteményből, és feldolgozásra kerül. A ciklus addig ismétlődik, amíg a gyűjtemény nem tartalmaz több feladatot.

Előfeltételek

Ezt az oktatóanyagot az alábbi lehetőségek egyikével követheti:

  • Visual Studio 2022 a .NET asztali fejlesztési munkaállomással telepítve. A számítási feladat kiválasztásakor a rendszer automatikusan telepíti a .NET SDK-t.
  • A .NET SDK valamilyen kódszerkesztővel a választásod szerint, például a Visual Studio Code-tal.

Példaalkalmazás létrehozása

Hozzon létre egy új .NET Core-konzolalkalmazást. Létrehozhat egyet a dotnet új konzolparancsával vagy a Visual Studióból.

Nyissa meg a Program.cs fájlt a kódszerkesztőben, és cserélje le a meglévő kódot a következő kódra:

using System.Diagnostics;

namespace ProcessTasksAsTheyFinish;

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

Mezők hozzáadása

Program Az osztálydefinícióban adja hozzá a következő két mezőt:

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

Ez HttpClient lehetővé teszi HTTP-kérések küldését és HTTP-válaszok fogadását. Az s_urlList alkalmazás által feldolgozni kívánt összes URL-címet tartalmazza.

Alkalmazás belépési pont frissítése

A konzolalkalmazás fő belépési pontja a Main metódus. Cserélje le a meglévő metódust a következőre:

static Task Main() => SumPageSizesAsync();

A frissített Main metódus mostantól Aszinkron főnek minősül, amely lehetővé teszi aszinkron belépési pontot a végrehajtható fájlba. Ez hívásként van kifejezve SumPageSizesAsync.

Az aszinkron oldal méretek összeadásának metódusát létrehozni

A Main metódus alatt adja hozzá a SumPageSizesAsync metódust.

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

A while ciklus eltávolítja az egyes iterációk egyik feladatát. Miután minden feladat befejeződött, a hurok befejeződik. A metódus a Stopwatch példányosításával és indításával kezdődik. Ezután tartalmaz egy lekérdezést, amely végrehajtásakor feladatgyűjteményt hoz létre. A következő kódban minden ProcessUrlAsync hívás egy Task<TResult> ad vissza, ahol TResult egy egész szám.

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

A LINQ-val történő halasztott végrehajtás miatt az egyes tevékenységek indítására kell hívnia Enumerable.ToList .

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

A while hurok a következő lépéseket hajtja végre a gyűjtemény minden egyes feladatához:

  1. WhenAny-ra várakozik, hogy azonosítsa a gyűjteményben azt az első feladatot, amely befejezte a letöltést.

    Task<int> finishedTask = await Task.WhenAny(downloadTasks);
    
  2. Azt a feladatot eltávolítja a gyűjteményből.

    downloadTasks.Remove(finishedTask);
    
  3. Várható, hogy a(z) finishedTask visszatér egy ProcessUrlAsync hívás eredményeként. A finishedTask változó egy egész TResult szám, ahol a Task<TResult>. A feladat már készen van, de vár arra, hogy kiderüljön a letöltött weboldal mérete, ahogy az alábbi példa is mutatja. Ha a feladat hibás, await az első gyermekkivételt dobja, amit a AggregateException tárol, ellentétben a Task<TResult>.Result tulajdonság olvasásával, amely a AggregateException kivételt dobná.

    total += await finishedTask;
    

Folyamatmetódus hozzáadása

Adja hozzá a következő ProcessUrlAsync metódust a SumPageSizesAsync metódus alá:

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

Bármely megadott URL-cím esetében a metódus a client megadott példányt használja a válasz byte[] lekéréséhez. A rendszer az URL-cím és a hossz konzolra írása után adja vissza a hosszt.

Futtassa többször a programot annak ellenőrzéséhez, hogy a letöltött hosszok nem mindig ugyanabban a sorrendben jelennek-e meg.

Caution

A példában ismertetett ciklusban olyan problémák megoldására használhatja WhenAny , amelyek kis számú feladatot foglalnak magukban. Más megközelítések azonban hatékonyabbak, ha sok feladatot kell feldolgoznia. További információkért és példákért tekintse meg a Feladatok feldolgozása, ahogy befejeződnek.

"Egyszerűsítse a megközelítést a Task.WhenEach segítségével"

A while metódusban SumPageSizesAsync implementált hurok egyszerűsíthető a .NET 9-ben bevezetett új Task.WhenEach metódus használatával, ha azt a await foreach hurokban hívja meg.
Cseréljük le a korábban megvalósított while hurkot:

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

az egyszerűsített await foreach:

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

Az új megközelítés lehetővé teszi, hogy ne kelljen többé ismételten Task.WhenAny-t hívni egy feladat manuális elvégzésére, és eltávolítani azt, amelyik befejeződött, mert Task.WhenEach a feladatokon a befejezésük sorrendjében iterál.

Teljes példa

A következő kód a példához tartozó Program.cs fájl teljes szövege.

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

Lásd még