Megosztás a következőn keresztül:


Aszinkron feladatok feldolgozása befejezett állapotban (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 időhurok minden iterációjában egy várt hívás WhenAny adja vissza a feladatot a letöltést először befejező tevékenységek gyűjteményében. A rendszer eltávolítja a feladatot a gyűjteményből, és feldolgozzák. 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 egy tetszőleges kódszerkesztővel, 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 a hívásként SumPageSizesAsyncvan kifejezve.

Az aszinkron sum page sizes metódus létrehozása

Main A metódus alatt adja hozzá a metódustSumPageSizesAsync:

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 tevékenység befejeződött, a hurok véget ér. A metódus a példányosítással kezdődik, és elindít egy Stopwatch. Ezután tartalmaz egy lekérdezést, amely végrehajtásakor feladatgyűjteményt hoz létre. A következő kód minden hívása ProcessUrlAsync egy Task<TResult>, ahol TResult egy egész szám szerepel:

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. Hívásra WhenAny vár a letöltést befejező gyűjtemény első feladatának azonosítására.

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

    downloadTasks.Remove(finishedTask);
    
  3. finishedTaskVár, amelyet egy hívás ad ProcessUrlAsyncvissza. A finishedTask változó egy Task<TResult> egész TResult szám. A feladat már befejeződött, de várja, hogy lekérje a letöltött webhely hosszát, ahogy az alábbi példa is mutatja. Ha a feladat hibás, await akkor a tulajdonság olvasásával Task<TResult>.Result ellentétben az első gyermekkivételt a program a következőben AggregateExceptiontárolja:AggregateException

    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 befejezettként című témakört.

A módszer egyszerűsítése Task.WhenEach

A while metódusban SumPageSizesAsync implementált hurok a .NET 9-ben bevezetett új Task.WhenEach metódussal egyszerűsíthető úgy, hogy hurkot await foreach hív meg.
Cserélje le a korábban implementált hurkot while :

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

Ez az új megközelítés lehetővé teszi, hogy többé ne hívjon Task.WhenAny meg manuálisan egy tevékenységet, és távolítsa el azt, amelyik befejeződött, mert Task.WhenEach a feladat végigfut a befejezés sorrendjében.

Példa kitöltése

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