Condividi tramite


Elaborare le attività asincrone al termine (C#)

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:

  1. Attende una chiamata a per WhenAny identificare la prima attività nella raccolta che ha terminato il download.

    Task<int> finishedTask = await Task.WhenAny(downloadTasks);
    
  2. Rimuove l'attività dalla raccolta.

    downloadTasks.Remove(finishedTask);
    
  3. finishedTaskAttende , restituito da una chiamata a ProcessUrlAsync. La finishedTask variabile è un Task<TResult> oggetto dove TResult è 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, await genererà la prima eccezione figlio archiviata in AggregateException, a differenza della lettura della Task<TResult>.Result proprietà , che genererà l'eccezione AggregateException.

    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

Vedere anche