Ausführen mehrerer asynchroner Aufgaben und Warten auf deren Abschluss

265

Ich muss mehrere asynchrone Aufgaben in einer Konsolenanwendung ausführen und warten, bis sie alle abgeschlossen sind, bevor ich sie weiter verarbeite.

Es gibt viele Artikel da draußen, aber ich scheine umso verwirrter zu werden, je mehr ich lese. Ich habe die Grundprinzipien der Aufgabenbibliothek gelesen und verstanden, aber irgendwo fehlt mir eindeutig ein Link.

Ich verstehe, dass es möglich ist, Aufgaben so zu verketten, dass sie nach Abschluss eines anderen beginnen (was so ziemlich das Szenario für alle Artikel ist, die ich gelesen habe), aber ich möchte, dass alle meine Aufgaben gleichzeitig ausgeführt werden, und ich möchte es einmal wissen Sie sind alle fertig.

Was ist die einfachste Implementierung für ein solches Szenario?

Daniel Minnaar
quelle

Antworten:

440

In beiden Antworten wurde das Erwartete nicht erwähnt Task.WhenAll:

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

await Task.WhenAll(task1, task2);

Der Hauptunterschied zwischen Task.WaitAllund Task.WhenAllbesteht darin, dass erstere blockiert werden (ähnlich wie bei Waiteiner einzelnen Aufgabe), während letztere nicht abgewartet werden können und dem Anrufer die Kontrolle zurückgeben, bis alle Aufgaben abgeschlossen sind.

Darüber hinaus unterscheidet sich die Ausnahmebehandlung:

Task.WaitAll::

Mindestens eine der Task-Instanzen wurde abgebrochen, oder während der Ausführung mindestens einer der Task-Instanzen wurde eine Ausnahme ausgelöst. Wenn eine Aufgabe abgebrochen wurde, enthält die AggregateException eine OperationCanceledException in ihrer InnerExceptions-Auflistung.

Task.WhenAll::

Wenn eine der bereitgestellten Aufgaben in einem fehlerhaften Zustand ausgeführt wird, wird die zurückgegebene Aufgabe auch in einem fehlerhaften Zustand ausgeführt, in dem ihre Ausnahmen die Aggregation der nicht ausgepackten Ausnahmen aus jeder der bereitgestellten Aufgaben enthalten.

Wenn keine der bereitgestellten Aufgaben fehlerhaft ist, aber mindestens eine abgebrochen wurde, endet die zurückgegebene Aufgabe im Status Abgebrochen.

Wenn keine der Aufgaben fehlerhaft ist und keine der Aufgaben abgebrochen wurde, endet die resultierende Aufgabe im Status RanToCompletion. Wenn das bereitgestellte Array / die Aufzählung keine Aufgaben enthält, wechselt die zurückgegebene Aufgabe sofort in einen RanToCompletion-Status, bevor sie an den Aufrufer zurückgegeben wird.

Yuval Itzchakov
quelle
4
Wenn ich das versuche, werden meine Aufgaben nacheinander ausgeführt? Muss man jede Aufgabe vorher einzeln starten await Task.WhenAll(task1, task2);?
Zapnologica
4
@Zapnologica Task.WhenAllstartet die Aufgaben nicht für Sie. Sie müssen sie "heiß" bereitstellen, dh bereits gestartet.
Yuval Itzchakov
2
OK. Das macht Sinn. Was wird Ihr Beispiel also tun? Weil du sie nicht gestartet hast?
Zapnologica
2
@ YuvalItzchakov vielen Dank! Es ist so einfach, aber es hat mir heute sehr geholfen! Ist mindestens +1000 wert :)
Daniel Dušek
1
@ Pierre Ich folge nicht. Was hat das StartNewDrehen neuer Aufgaben mit dem asynchronen Warten auf alle zu tun?
Yuval Itzchakov
106

Sie können viele Aufgaben erstellen wie:

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

Task.WaitAll(TaskList.ToArray());
Virus
quelle
48
Ich würde WhenAll
Ravi
Ist es möglich, mehrere neue Threads gleichzeitig mit dem Schlüsselwort await anstelle von .Start () zu starten?
Matt W
1
@MattW Nein, wenn Sie wait verwenden, wird es warten, bis es abgeschlossen ist. In diesem Fall können Sie keine Multithread-Umgebung erstellen. Dies ist der Grund, warum alle Aufgaben am Ende der Schleife gewartet werden.
Virus
5
Downvote für zukünftige Leser, da nicht klar ist, dass dies ein blockierender Anruf ist.
JRoughan
In der akzeptierten Antwort finden Sie Gründe, warum Sie dies nicht tun sollten.
EL MOJO
26

Die beste Option, die ich gesehen habe, ist die folgende Erweiterungsmethode:

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

Nennen Sie es so:

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

Oder mit einem asynchronen Lambda:

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

Sie können verwenden, WhenAllwelche einen erwarteten Taskoder WaitAllkeinen Rückgabetyp zurückgeben und die weitere Codeausführung blockieren, Thread.Sleepbis alle Aufgaben abgeschlossen, abgebrochen oder fehlerhaft sind.

Geben Sie hier die Bildbeschreibung ein

Beispiel

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

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

Wenn Sie die Aufgaben in einer bestimmten Reihenfolge ausführen möchten, können Sie sich von dieser Antwort inspirieren lassen.

NtFreX
quelle
Es tut mir leid, dass Sie zu spät zur Party gekommen sind, aber warum müssen Sie awaitfür jede Operation und gleichzeitig verwenden WaitAlloder WhenAll. Sollten Aufgaben bei der Task[]Initialisierung nicht ohne sein await?
dee zg
@dee zg Du hast recht. Das Warten oben besiegt den Zweck. Ich werde meine Antwort ändern und sie entfernen.
NtFreX
ja das ist es. Danke für die Klarstellung! (Upvote für nette Antwort)
dee zg
8

Möchten Sie die Tasks verketten oder können sie parallel aufgerufen werden?

Zum Verketten
Mach einfach so etwas

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

und vergessen Sie nicht, die vorherige TaskInstanz in jeder zu überprüfen, ContinueWithda dies möglicherweise fehlerhaft ist.

Für die parallele Art
Die einfachste Methode, auf die ich gestoßen bin: Parallel.Invoke Andernfalls gibt es Task.WaitAlloder Sie können sogar WaitHandles verwenden, um einen Countdown bis zu null verbleibenden Aktionen durchzuführen (warten Sie, es gibt eine neue Klasse :) CountdownEventoder ...

Andreas Niedermair
quelle
3
Schätzen Sie die Antwort, aber Ihre Vorschläge hätten etwas näher erläutert werden können.
Daniel Minnaar
@drminnaar Welche andere Erklärung neben den Links zu msdn mit Beispielen brauchst du? Sie haben nicht einmal auf die Links geklickt, oder?
Andreas Niedermair
4
Ich habe auf die Links geklickt und den Inhalt gelesen. Ich wollte Invoke, aber es gab viele Wenn und Aber, ob es asynchron läuft oder nicht. Sie haben Ihre Antwort kontinuierlich bearbeitet. Der von Ihnen gepostete WaitAll-Link war perfekt, aber ich habe mich für die Antwort entschieden, die die gleiche Funktionalität schneller und einfacher zu lesen zeigt. Seien Sie nicht beleidigt, Ihre Antwort bietet immer noch gute Alternativen zu anderen Ansätzen.
Daniel Minnaar
@drminnaar keine Beleidigung hier genommen, ich bin nur neugierig :)
Andreas Niedermair
5

So mache ich es mit einem 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
DalSoft
quelle
1
Warum behalten Sie es nicht einfach als Task-Array?
Talha Talip Açıkgöz
1
Wenn Sie @ talha-talip-açıkgöz nicht vorsichtig sind, führen Sie die Aufgaben aus, wenn Sie nicht damit gerechnet haben, dass sie ausgeführt werden. Wenn Sie dies als Func-Delegierter tun, wird Ihre Absicht klar.
DalSoft
5

Noch eine Antwort ... aber ich befinde mich normalerweise in einem Fall, in dem ich Daten gleichzeitig laden und in Variablen einfügen muss, wie:

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
}
Yehor Hromadskyi
quelle
1
Wenn LoadCatsAsync()und LoadDogAsync()nur Datenbankaufrufe sind, sind sie E / A-gebunden. Task.Run()ist für CPU-gebundene Arbeit; Es fügt zusätzlichen unnötigen Overhead hinzu, wenn Sie nur auf eine Antwort vom Datenbankserver warten. Yuvals akzeptierte Antwort ist der richtige Weg für IO-gebundene Arbeit.
Stephen Kennedy
@StephenKennedy Könnten Sie bitte klären, welche Art von Overhead und wie stark sich dies auf die Leistung auswirken kann? Vielen Dank!
Yehor Hromadskyi
Das wäre im Kommentarfeld ziemlich schwer zusammenzufassen :) Stattdessen empfehle ich, Stephen Clearys Artikel zu lesen - er ist ein Experte für dieses Zeug. Beginnen Sie hier: blog.stephencleary.com/2013/10/…
Stephen Kennedy
-1

Ich habe einen Code vorbereitet, um Ihnen zu zeigen, wie Sie die Aufgabe für einige dieser Szenarien verwenden können.

    // 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);
    }
sayah imad
quelle
1
Wie erhalten Sie die Ergebnisse von Aufgaben? Zum Beispiel, um "Zeilen" (von N Aufgaben parallel) in einer Datentabelle zusammenzuführen und an gridview asp.net zu binden?
PreguntonCojoneroCabrón