Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Usando Task.WhenAny, è possibile avviare più attività contemporaneamente ed elaborarle una alla volta man mano che vengono completate anziché elaborarle nell'ordine in cui vengono avviate.
Nell'esempio seguente viene utilizzata una query per creare una raccolta di attività. Ogni attività scarica il contenuto di un sito Web specificato. In ogni iterazione di un ciclo while, una chiamata attesa per WhenAny restituire l'attività nella raccolta di attività che completa il download per primo. Tale attività viene rimossa dalla raccolta ed elaborata. Il ciclo si ripete fino a quando la raccolta non contiene altre attività.
Prerequisiti
È possibile seguire questa esercitazione usando una delle opzioni seguenti:
- Visual Studio 2022 con il carico di lavoro per lo sviluppo 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 uno 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 Program classe 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://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"
};
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 Main metodo . Sostituire il metodo esistente con quanto segue:
static Task Main() => SumPageSizesAsync();
Il metodo aggiornato Main è ora considerato un main asincrono, che consente un punto di ingresso asincrono nel file eseguibile. Viene espresso come chiamata a SumPageSizesAsync.
Creare il metodo asincrono dimensioni pagina somma
Sotto il Main metodo aggiungere il SumPageSizesAsync metodo :
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 while ciclo rimuove una delle attività in ogni iterazione. Al termine di ogni attività, il ciclo termina. Il metodo inizia creando un'istanza e avviando un oggetto Stopwatch. Include quindi una query che, quando eseguita, crea una raccolta di attività. Ogni chiamata a ProcessUrlAsync nel codice seguente restituisce un oggetto Task<TResult>, dove TResult è un numero 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 while ciclo esegue i passaggi seguenti per ogni attività nella raccolta:
Attende una chiamata a per
WhenAnyidentificare la prima attività nella raccolta che ha terminato il download.Task<int> finishedTask = await Task.WhenAny(downloadTasks);Rimuove l'attività dalla raccolta.
downloadTasks.Remove(finishedTask);finishedTaskAttende , restituito da una chiamata aProcessUrlAsync. LafinishedTaskvariabile è un Task<TResult> oggetto doveTResultè un numero intero. L'attività è già stata completata, ma si attende che venga recuperata la lunghezza del sito Web scaricato, come illustrato nell'esempio seguente. Se l'attività è in errore,awaitgenererà la prima eccezione figlio archiviata inAggregateException, a differenza della lettura della Task<TResult>.Result proprietà , che genererà l'eccezioneAggregateException.total += await finishedTask;
Add process method
Aggiungere il metodo seguente sotto ProcessUrlAsync il SumPageSizesAsync metodo :
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 client fornita per ottenere la risposta come .byte[] La lunghezza viene restituita dopo la scrittura dell'URL e della lunghezza nella console.
Eseguire il programma più volte per verificare che le lunghezze scaricate non vengano sempre visualizzate nello stesso ordine.
Attenzione
È possibile usare WhenAny in un ciclo, come descritto nell'esempio, per risolvere i problemi che coinvolgono un numero ridotto di attività. Tuttavia, altri approcci sono più efficienti se si dispone di un numero elevato di attività da elaborare. Per altre informazioni ed esempi, vedere Elaborazione delle attività non appena completate.
Semplificare l'approccio usando Task.WhenEach
Il while ciclo implementato nel SumPageSizesAsync metodo può essere semplificato usando il nuovo Task.WhenEach metodo introdotto in .NET 9, chiamandolo in await foreach ciclo .
Sostituire il ciclo implementato while in precedenza:
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(finishedTask);
total += await finishedTask;
}
con l'oggetto await foreachsemplificato :
await foreach (Task<int> t in Task.WhenEach(downloadTasks))
{
total += await t;
}
Questo nuovo approccio consente di non chiamare Task.WhenAny più ripetutamente per chiamare manualmente un'attività e rimuovere quello che termina, perché Task.WhenEach scorre l'attività in un ordine di completamento.
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://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