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://video2.skills-academy.com",
"https://video2.skills-academy.com/aspnet/core",
"https://video2.skills-academy.com/azure",
"https://video2.skills-academy.com/azure/devops",
"https://video2.skills-academy.com/dotnet",
"https://video2.skills-academy.com/dynamics365",
"https://video2.skills-academy.com/education",
"https://video2.skills-academy.com/enterprise-mobility-security",
"https://video2.skills-academy.com/gaming",
"https://video2.skills-academy.com/graph",
"https://video2.skills-academy.com/microsoft-365",
"https://video2.skills-academy.com/office",
"https://video2.skills-academy.com/powershell",
"https://video2.skills-academy.com/sql",
"https://video2.skills-academy.com/surface",
"https://video2.skills-academy.com/system-center",
"https://video2.skills-academy.com/visualstudio",
"https://video2.skills-academy.com/windows",
"https://video2.skills-academy.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:
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);
Tar bort uppgiften från samlingen.
downloadTasks.Remove(finishedTask);
Awaits
finishedTask
, som returneras av ett anrop tillProcessUrlAsync
. VariabelnfinishedTask
är ett Task<TResult> därTResult
ä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 ärawait
felaktig genererar det första underordnade undantaget som lagras iAggregateException
, till skillnad från att läsa Task<TResult>.Result egenskapen , vilket skulle utlösaAggregateException
.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://video2.skills-academy.com",
"https://video2.skills-academy.com/aspnet/core",
"https://video2.skills-academy.com/azure",
"https://video2.skills-academy.com/azure/devops",
"https://video2.skills-academy.com/dotnet",
"https://video2.skills-academy.com/dynamics365",
"https://video2.skills-academy.com/education",
"https://video2.skills-academy.com/enterprise-mobility-security",
"https://video2.skills-academy.com/gaming",
"https://video2.skills-academy.com/graph",
"https://video2.skills-academy.com/microsoft-365",
"https://video2.skills-academy.com/office",
"https://video2.skills-academy.com/powershell",
"https://video2.skills-academy.com/sql",
"https://video2.skills-academy.com/surface",
"https://video2.skills-academy.com/system-center",
"https://video2.skills-academy.com/visualstudio",
"https://video2.skills-academy.com/windows",
"https://video2.skills-academy.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://video2.skills-academy.com 132,517
// https://video2.skills-academy.com/powershell 57,375
// https://video2.skills-academy.com/gaming 33,549
// https://video2.skills-academy.com/aspnet/core 88,714
// https://video2.skills-academy.com/surface 39,840
// https://video2.skills-academy.com/enterprise-mobility-security 30,903
// https://video2.skills-academy.com/microsoft-365 67,867
// https://video2.skills-academy.com/windows 26,816
// https://video2.skills-academy.com/maui 57,958
// https://video2.skills-academy.com/dotnet 78,706
// https://video2.skills-academy.com/graph 48,277
// https://video2.skills-academy.com/dynamics365 49,042
// https://video2.skills-academy.com/office 67,867
// https://video2.skills-academy.com/system-center 42,887
// https://video2.skills-academy.com/education 38,636
// https://video2.skills-academy.com/azure 421,663
// https://video2.skills-academy.com/visualstudio 30,925
// https://video2.skills-academy.com/sql 54,608
// https://video2.skills-academy.com/azure/devops 86,034
// Total bytes returned: 1,454,184
// Elapsed time: 00:00:01.1290403