Esecuzione di più attività asincrone e in attesa che vengano completate tutte


265

Devo eseguire più attività asincrone in un'applicazione console e attendere il completamento di tutte prima di un'ulteriore elaborazione.

Ci sono molti articoli là fuori, ma mi sembra di essere più confuso più leggo. Ho letto e compreso i principi di base della libreria Task, ma mi manca chiaramente un link da qualche parte.

Comprendo che è possibile concatenare le attività in modo che inizino dopo che un altro è stato completato (che è praticamente lo scenario di tutti gli articoli che ho letto), ma voglio che tutte le mie attività vengano eseguite contemporaneamente e voglio sapere una volta sono tutti completati.

Qual è l'implementazione più semplice per uno scenario come questo?

Risposte:


441

Entrambe le risposte non hanno menzionato l'atteso Task.WhenAll:

var task1 = DoWorkAsync();
var task2 = DoMoreWorkAsync();

await Task.WhenAll(task1, task2);

La differenza principale tra Task.WaitAlle Task.WhenAllè che la prima si bloccherà (simile all'utilizzo Waitsu una singola attività) mentre la seconda non lo farà e potrà essere attesa, restituendo il controllo al chiamante fino al termine di tutte le attività.

Inoltre, la gestione delle eccezioni differisce:

Task.WaitAll:

Almeno una delle istanze dell'attività è stata annullata - oppure è stata generata un'eccezione durante l'esecuzione di almeno una delle istanze dell'attività. Se un'attività è stata annullata, AggregateException contiene OperationCanceledException nella sua raccolta InnerExceptions.

Task.WhenAll:

Se una delle attività fornite viene completata in uno stato di errore, l'attività restituita verrà completata anche in uno stato di errore, in cui le sue eccezioni conterranno l'aggregazione dell'insieme di eccezioni non scartate da ciascuna delle attività fornite.

Se nessuna delle attività fornite ha avuto un errore ma almeno una è stata annullata, l'attività restituita terminerà nello stato Annullato.

Se nessuna delle attività si è guastata e nessuna delle attività è stata annullata, l'attività risultante termina nello stato RanToCompletion. Se l'array / l'enumerabile fornito non contiene attività, l'attività restituita passerà immediatamente allo stato RanToCompletion prima di essere restituita al chiamante.


4
Quando provo questo i miei compiti si svolgono in sequenza? Uno deve iniziare ogni attività individualmente prima await Task.WhenAll(task1, task2);?
Zapnologica,

4
@Zapnologica Task.WhenAllnon avvia i compiti per te. Devi fornire loro "hot", che significa già avviato.
Yuval Itzchakov,

2
Ok. Questo ha senso. Quindi cosa farà il tuo esempio? Perché non li hai avviati?
Zapnologica,

2
@YuvalItzchakov grazie mille! È così semplice ma mi ha aiutato molto oggi! Vale almeno +1000 :)
Daniel Dušek il

1
@Pierre non sto seguendo. Cosa StartNewhanno a che fare con la rotazione di nuove attività con l'attesa asincrona di tutte?
Yuval Itzchakov,

106

È possibile creare molte attività come:

List<Task> TaskList = new List<Task>();
foreach(...)
{
   var LastTask = new Task(SomeFunction);
   LastTask.Start();
   TaskList.Add(LastTask);
}

Task.WaitAll(TaskList.ToArray());

48
Consiglierei WhenAll
Ravi il

È possibile avviare più nuovi thread contemporaneamente utilizzando la parola chiave waitit anziché .Start ()?
Matt W

1
@MattW No, quando si utilizza wait, aspetterebbe che si completasse. In questo caso non saresti in grado di creare un ambiente multi-thread. Questo è il motivo per cui tutte le attività sono attese alla fine del ciclo.
Virus

5
Votazione per i lettori futuri poiché non è stato chiarito che si tratta di una chiamata bloccante.
JRoughan,

Vedi la risposta accettata per i motivi per cui non farlo.
EL MOJO,

26

L'opzione migliore che ho visto è il seguente metodo di estensione:

public static Task ForEachAsync<T>(this IEnumerable<T> sequence, Func<T, Task> action) {
    return Task.WhenAll(sequence.Select(action));
}

Chiamalo così:

await sequence.ForEachAsync(item => item.SomethingAsync(blah));

O con un lambda asincrono:

await sequence.ForEachAsync(async item => {
    var more = await GetMoreAsync(item);
    await more.FrobbleAsync();
});

26

È possibile utilizzare WhenAllciò che restituirà un valore attendibile Tasko WaitAllche non ha alcun tipo di restituzione e bloccherà l'ulteriore esecuzione del codice in modo simile Thread.Sleepfino a quando tutte le attività non saranno completate, annullate o guastate.

inserisci qui la descrizione dell'immagine

Esempio

var tasks = new Task[] {
    TaskOperationOne(),
    TaskOperationTwo()
};

Task.WaitAll(tasks);
// or
await Task.WhenAll(tasks);

Se vuoi eseguire i compiti in un ordine pratico puoi trarre ispirazione da questa risposta.


scusate se siete venuti tardi alla festa, ma perché avete awaitogni operazione e allo stesso tempo usate WaitAllo WhenAll. Le attività nell'inizializzazione non dovrebbero Task[]essere senza await?
dee zg

@dee zg Hai ragione. L'attesa sopra sconfigge lo scopo. Cambierò la mia risposta e le rimuoverò.
NtFreX

sì è quello. grazie per il chiarimento! (votato per una bella risposta)
dee zg

8

Vuoi concatenare le Tasks o possono essere invocate in modo parallelo?

Per concatenare
Basta fare qualcosa di simile

Task.Run(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);
Task.Factory.StartNew(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);

e non dimenticare di controllare l' Taskistanza precedente in ciascuna ContinueWithpoiché potrebbe essere difettosa.

Per il modo parallelo
Il metodo più semplice che ho incontrato: Parallel.Invoke Altrimenti c'è Task.WaitAllo puoi usare WaitHandles per fare un conto alla rovescia per zero azioni rimaste (aspetta, c'è una nuova classe:) CountdownEvent, o ...


3
Apprezzo la risposta, ma i tuoi suggerimenti avrebbero potuto essere spiegati un po 'di più.
Daniel Minnaar,

@drminnaar quali altre spiegazioni oltre ai collegamenti a msdn con esempi ti servono? non hai nemmeno cliccato sui link, vero?
Andreas Niedermair,

4
Ho cliccato sui link e ho letto il contenuto. Stavo per Invoke, ma c'erano molti If e But riguardo al fatto che funzionasse in modo asincrono o meno. Stavi modificando la tua risposta continuamente. Il link WaitAll che hai pubblicato era perfetto, ma ho cercato la risposta che dimostrava la stessa funzionalità in un modo più veloce e più facile da leggere. Non offenderti, la tua risposta offre comunque buone alternative ad altri approcci.
Daniel Minnaar,

@drminnaar nessuna offesa presa qui, sono solo curioso :)
Andreas Niedermair,

5

Ecco come lo faccio con un array Func <> :

var tasks = new Func<Task>[]
{
   () => myAsyncWork1(),
   () => myAsyncWork2(),
   () => myAsyncWork3()
};

await Task.WhenAll(tasks.Select(task => task()).ToArray()); //Async    
Task.WaitAll(tasks.Select(task => task()).ToArray()); //Or use WaitAll for Sync

1
Perché non lo tieni solo come Task Array?
Talha Talip Açıkgöz,

1
Se non stai attento @ talha-talip-açıkgöz esegui i compiti quando non ti aspettavi che li eseguissero. Farlo come delegato di Func rende chiaro il tuo intento.
DalSoft,

5

Ancora un'altra risposta ... ma di solito mi ritrovo in un caso, quando ho bisogno di caricare i dati contemporaneamente e metterli in variabili, come:

var cats = new List<Cat>();
var dog = new Dog();

var loadDataTasks = new Task[]
{
    Task.Run(async () => cats = await LoadCatsAsync()),
    Task.Run(async () => dog = await LoadDogAsync())
};

try
{
    await Task.WhenAll(loadDataTasks);
}
catch (Exception ex)
{
    // handle exception
}

1
Se LoadCatsAsync()e LoadDogAsync()sono solo chiamate al database, sono associate a IO. Task.Run()è per lavoro associato alla CPU; aggiunge un sovraccarico non necessario aggiuntivo se tutto ciò che si sta facendo è in attesa di una risposta dal server di database. La risposta accettata da Yuval è la strada giusta per il lavoro legato all'IO.
Stephen Kennedy,

@StephenKennedy potresti chiarire per favore che tipo di spese generali e quanto può influire sulle prestazioni? Grazie!
Yehor Hromadskyi,

Sarebbe piuttosto difficile riassumere nella casella dei commenti :) Invece raccomando di leggere gli articoli di Stephen Cleary - è un esperto di queste cose. Inizia qui: blog.stephencleary.com/2013/10/…
Stephen Kennedy l'

-1

Ho preparato un pezzo di codice per mostrarti come utilizzare l'attività per alcuni di questi scenari.

    // method to run tasks in a parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks) {

        await Task.WhenAll(tasks);
    }
    // methode to run task one by one 
    public async Task RunMultipleTaskOneByOne(Task[] tasks)
    {
        for (int i = 0; i < tasks.Length - 1; i++)
            await tasks[i];
    }
    // method to run i task in parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks, int i)
    {
        var countTask = tasks.Length;
        var remainTasks = 0;
        do
        {
            int toTake = (countTask < i) ? countTask : i;
            var limitedTasks = tasks.Skip(remainTasks)
                                    .Take(toTake);
            remainTasks += toTake;
            await RunMultipleTaskParallel(limitedTasks.ToArray());
        } while (remainTasks < countTask);
    }

1
come ottengono i risultati di Compiti? Ad esempio, per unire "righe" (da N attività in parallelo) in un datatable e collegarlo a gridview asp.net?
PreguntonCojoneroCabrón
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.