Elaborare le attività asincrone non appena vengono completate (C#)
Usando Task.WhenAny, è possibile avviare più attività contemporaneamente ed elaborarle una ad una quando vengono completate, invece che nell'ordine in cui vengono avviate.
Nell'esempio seguente viene usata una query per creare una Collection di attività. Ogni attività scarica il contenuto di un sito Web specificato. In ogni iterazione di un ciclo while, una chiamata attesa a WhenAny restituisce l'attività nella Collection di attività che completa per prima il download. Questa attività viene rimossa dalla Collection ed elaborata. Il ciclo si ripete finché la Collection non contiene più attività.
Prerequisiti
È possibile seguire questa esercitazione usando una delle opzioni seguenti:
- Visual Studio 2022 con il carico di lavoro Sviluppo per desktop .NET installato. .NET SDK viene installato automaticamente quando si seleziona questo carico di lavoro.
- .NET SDK con un editor di codice di propria scelta, ad esempio Visual Studio Code.
Creare un'applicazione di esempio
Creare una nuova applicazione console .NET Core. È possibile crearne una usando il comando dotnet new console o da Visual Studio.
Aprire il file Program.cs nell'editor di codice e sostituire il codice esistente con questo codice:
using System.Diagnostics;
namespace ProcessTasksAsTheyFinish;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
Aggiungi campi
Nella definizione della classe Program
aggiungere i due campi seguenti:
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
espone la possibilità di inviare richieste HTTP e ricevere risposte HTTP. s_urlList
contiene tutti gli URL che l'applicazione prevede di elaborare.
Aggiornare il punto di ingresso dell'applicazione
Il punto di ingresso principale nell'applicazione console è il metodo Main
. Sostituire il metodo esistente con quanto segue:
static Task Main() => SumPageSizesAsync();
Il metodo Main
aggiornato viene ora considerato un Async Main, che consente un punto di ingresso asincrono nel file eseguibile. Viene espresso come chiamata a SumPageSizesAsync
.
Creare il metodo SumPageSizes asincrono
Sotto il metodo Main
aggiungere il metodo SumPageSizesAsync
:
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");
}
Il ciclo while
rimuove una delle attività in ogni iterazione. Al termine di ogni attività, il ciclo termina. Il metodo inizia creando un'istanza e avviando una classe Stopwatch. Include quindi una query che, quando eseguita, crea una raccolta di attività. Ogni chiamata a ProcessUrlAsync
nel codice seguente restituisce un Task<TResult>, dove TResult
è un valore intero:
IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);
A causa dell'esecuzione posticipata con LINQ, si chiama Enumerable.ToList per avviare ogni attività.
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
Il ciclo while
esegue i passaggi seguenti per ogni attività nella raccolta:
Attende una chiamata a
WhenAny
per identificare la prima attività nella raccolta che ha terminato il download.Task<int> finishedTask = await Task.WhenAny(downloadTasks);
Rimuove l'attività dalla Collection.
downloadTasks.Remove(finishedTask);
Attende
finishedTask
, che viene restituito da una chiamata aProcessUrlAsync
. La variabilefinishedTask
è un Task<TResult> doveTResult
è un valore intero. L'attività è già stata completata, ma è possibile metterla in attesa per recuperare la lunghezza del sito Web scaricato, come illustrato di seguito. Se l'attività è in errore,await
genererà la prima eccezione figlio archiviata inAggregateException
, a differenza della lettura della proprietà Task<TResult>.Result, che genererebbeAggregateException
.total += await finishedTask;
Aggiungere un metodo di elaborazione
Aggiungere il metodo ProcessUrlAsync
seguente sotto il metodo SumPageSizesAsync
:
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;
}
Per qualsiasi URL specificato, il metodo userà l'istanza di client
fornita per ottenere la risposta come byte[]
. La lunghezza viene restituita dopo l'URL e la lunghezza viene scritta nella console.
Eseguire il programma più volte per verificare che le lunghezze scaricate non siano sempre nello stesso ordine.
Attenzione
È possibile usare WhenAny
in un ciclo, come descritto nell'esempio, per risolvere i problemi che includono un numero limitato di attività. Tuttavia, se ci sono molte attività da elaborare, altri approcci sono più efficienti. Per altre informazioni ed esempi, vedere Processing tasks as they complete (Elaborazione di attività completate).
Esempio completo
Il codice seguente è il testo completo del file Program.cs per l'esempio.
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