Annullare un elenco di attività
Se non si vuole attendere il completamento, è possibile annullare un'applicazione console asincrona. Seguendo gli esempi in questo argomento, è possibile aggiungere l’annullamento di un'applicazione che scarica il contenuto di un elenco di siti Web. È possibile annullare molte attività associando l'istanza di CancellationTokenSource a ogni attività. Se si seleziona la chiave Invio, annullare tutte le attività non ancora completate.
Contenuto dell'esercitazione:
- Creazione di un'applicazione console .NET
- Scrittura di un'applicazione asincrona che supporta l'annullamento
- Dimostrazione dell'annullamento della segnalazione
Prerequisiti
Per completare questa esercitazione, è necessario disporre di:
- .NET 5 o versione successiva dell'SDK
- Ambiente di sviluppo integrato (IDE)
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 preferito.
Sostituire le istruzioni
Sostituire le istruzioni esistenti con queste dichiarazioni:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
Aggiungi campi
Nella definizione della classe Program
aggiungere questi tre campi:
static readonly CancellationTokenSource s_cts = new CancellationTokenSource();
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"
};
CancellationTokenSource viene utilizzato per segnalare un annullamento richiesto a un oggetto CancellationToken. 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 async Task Main()
{
Console.WriteLine("Application started.");
Console.WriteLine("Press the ENTER key to cancel...\n");
Task cancelTask = Task.Run(() =>
{
while (Console.ReadKey().Key != ConsoleKey.Enter)
{
Console.WriteLine("Press the ENTER key to cancel...");
}
Console.WriteLine("\nENTER key pressed: cancelling downloads.\n");
s_cts.Cancel();
});
Task sumPageSizesTask = SumPageSizesAsync();
Task finishedTask = await Task.WhenAny(new[] { cancelTask, sumPageSizesTask });
if (finishedTask == cancelTask)
{
// wait for the cancellation to take place:
try
{
await sumPageSizesTask;
Console.WriteLine("Download task completed before cancel request was processed.");
}
catch (TaskCanceledException)
{
Console.WriteLine("Download task has been cancelled.");
}
}
Console.WriteLine("Application ending.");
}
Il metodo Main
aggiornato viene ora considerato un Async Main, che consente un punto di ingresso asincrono nel file eseguibile. Scrive alcuni messaggi di istruzioni nella console, quindi dichiara un'istanza Task denominata cancelTask
, che leggerà i tratti chiave della console. Se viene premuto il tasto Invio, viene effettuata una chiamata a CancellationTokenSource.Cancel(). In questo modo verrà segnalato l'annullamento. Successivamente, la variabile sumPageSizesTask
viene assegnata dal SumPageSizesAsync
metodo. Entrambe le attività vengono quindi passate a Task.WhenAny(Task[]), che continuerà quando una delle due attività è stata completata.
Il blocco di codice successivo garantisce che l'applicazione non venga chiusa fino a quando l'annullamento non è stato elaborato. Se la prima attività da completare è cancelTask
, sumPageSizeTask
resta in attesa. Se è stato annullato, quando è in attesa genera un'eccezione System.Threading.Tasks.TaskCanceledException. Il blocco intercetta tale eccezione e stampa un messaggio.
Creare il metodo SumPageSizes asincrono
Sotto il metodo Main
aggiungere il metodo SumPageSizesAsync
:
static async Task SumPageSizesAsync()
{
var stopwatch = Stopwatch.StartNew();
int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
total += contentLength;
}
stopwatch.Stop();
Console.WriteLine($"\nTotal bytes returned: {total:#,#}");
Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}\n");
}
Il metodo inizia creando un'istanza e avviando una classe Stopwatch. Viene quindi eseguito il ciclo di ogni URL nell'oggetto s_urlList
e chiama ProcessUrlAsync
. Con ogni iterazione, il s_cts.Token
viene passato al metodo ProcessUrlAsync
e il codice restituisce un Task<TResult>, dove TResult
è un numero intero:
int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
total += contentLength;
}
Aggiungere un metodo di elaborazione
Aggiungere il metodo ProcessUrlAsync
seguente sotto il metodo SumPageSizesAsync
:
static async Task<int> ProcessUrlAsync(string url, HttpClient client, CancellationToken token)
{
HttpResponseMessage response = await client.GetAsync(url, token);
byte[] content = await response.Content.ReadAsByteArrayAsync(token);
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[]
. L'istanza CancellationToken viene passata ai HttpClient.GetAsync(String, CancellationToken) metodi e HttpContent.ReadAsByteArrayAsync(). token
viene utilizzato per la registrazione per l'annullamento richiesto. La lunghezza viene restituita dopo l'URL e la lunghezza viene scritta nella console.
Output dell'applicazione di esempio
Application started.
Press the ENTER key to cancel...
https://learn.microsoft.com 37,357
https://learn.microsoft.com/aspnet/core 85,589
https://learn.microsoft.com/azure 398,939
https://learn.microsoft.com/azure/devops 73,663
https://learn.microsoft.com/dotnet 67,452
https://learn.microsoft.com/dynamics365 48,582
https://learn.microsoft.com/education 22,924
ENTER key pressed: cancelling downloads.
Application ending.
Esempio completo
Il codice seguente è il testo completo del file Program.cs per l'esempio.
using System.Diagnostics;
class Program
{
static readonly CancellationTokenSource s_cts = new CancellationTokenSource();
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"
};
static async Task Main()
{
Console.WriteLine("Application started.");
Console.WriteLine("Press the ENTER key to cancel...\n");
Task cancelTask = Task.Run(() =>
{
while (Console.ReadKey().Key != ConsoleKey.Enter)
{
Console.WriteLine("Press the ENTER key to cancel...");
}
Console.WriteLine("\nENTER key pressed: cancelling downloads.\n");
s_cts.Cancel();
});
Task sumPageSizesTask = SumPageSizesAsync();
Task finishedTask = await Task.WhenAny(new[] { cancelTask, sumPageSizesTask });
if (finishedTask == cancelTask)
{
// wait for the cancellation to take place:
try
{
await sumPageSizesTask;
Console.WriteLine("Download task completed before cancel request was processed.");
}
catch (OperationCanceledException)
{
Console.WriteLine("Download task has been cancelled.");
}
}
Console.WriteLine("Application ending.");
}
static async Task SumPageSizesAsync()
{
var stopwatch = Stopwatch.StartNew();
int total = 0;
foreach (string url in s_urlList)
{
int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
total += contentLength;
}
stopwatch.Stop();
Console.WriteLine($"\nTotal bytes returned: {total:#,#}");
Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}\n");
}
static async Task<int> ProcessUrlAsync(string url, HttpClient client, CancellationToken token)
{
HttpResponseMessage response = await client.GetAsync(url, token);
byte[] content = await response.Content.ReadAsByteArrayAsync(token);
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");
return content.Length;
}
}