Frage Warum sollte ich einzelne 'awarte Task.WhenAll' über mehrere warten?


Wenn mir die Reihenfolge der Aufgabenerfüllung egal ist und sie alle benötigt werden, sollte ich sie trotzdem benutzen await Task.WhenAll anstelle von mehreren await? ZB, ist DoWord2 unter einer bevorzugten Methode zu DoWork1 (und warum?):

using System;
using System.Threading.Tasks;

namespace ConsoleApp
{
    class Program
    {
        static async Task<string> DoTaskAsync(string name, int timeout)
        {
            var start = DateTime.Now;
            Console.WriteLine("Enter {0}, {1}", name, timeout);
            await Task.Delay(timeout);
            Console.WriteLine("Exit {0}, {1}", name, (DateTime.Now - start).TotalMilliseconds);
            return name;
        }

        static async Task DoWork1()
        {
            var t1 = DoTaskAsync("t1.1", 3000);
            var t2 = DoTaskAsync("t1.2", 2000);
            var t3 = DoTaskAsync("t1.3", 1000);

            await t1; await t2; await t3;

            Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
        }

        static async Task DoWork2()
        {
            var t1 = DoTaskAsync("t2.1", 3000);
            var t2 = DoTaskAsync("t2.2", 2000);
            var t3 = DoTaskAsync("t2.3", 1000);

            await Task.WhenAll(t1, t2, t3);

            Console.WriteLine("DoWork2 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
        }


        static void Main(string[] args)
        {
            Task.WhenAll(DoWork1(), DoWork2()).Wait();
        }
    }
}

76
2017-08-19 09:56


Ursprung


Antworten:


Ja, benutzen WhenAll weil es alle Fehler auf einmal verbreitet. Mit den Mehrfachwarnungen verlieren Sie Fehler, wenn einer der früheren wartet.

Ein weiterer wichtiger Unterschied ist, dass WhenAll warten wird alle Aufgaben zu vervollständigen. Eine Kette von await würde abbrechen warten Bei der ersten Ausnahme wird die Ausführung nicht erwarteter Tasks fortgesetzt. Dies verursacht unerwarteten Nebenläufigkeit.

Ich denke, es erleichtert auch das Lesen des Codes, weil die gewünschte Semantik direkt im Code dokumentiert wird.


73
2017-08-19 10:02



Mein Verständnis ist das der Hauptgrund zu bevorzugen Task.WhenAll zu mehreren awaits ist Leistung / Aufgabe "Churning": die DoWork1 Methode macht so etwas:

  • Beginne mit einem gegebenen Kontext
  • Speichern Sie den Kontext
  • warte auf t1
  • Stellen Sie den ursprünglichen Kontext wieder her
  • Speichern Sie den Kontext
  • warte auf t2
  • Stellen Sie den ursprünglichen Kontext wieder her
  • Speichern Sie den Kontext
  • warte auf t3
  • Stellen Sie den ursprünglichen Kontext wieder her

Im Gegensatz, DoWork2 macht dies:

  • Beginnen Sie mit einem gegebenen Kontext
  • Speichern Sie den Kontext
  • warte auf alles von t1, t2 und t3
  • Stellen Sie den ursprünglichen Kontext wieder her

Ob dies für Ihren speziellen Fall eine ausreichend große Sache ist, ist natürlich "kontextabhängig" (entschuldigen Sie das Wortspiel).


19
2017-11-27 16:39



Eine asynchrone Methode wird als Zustandsmaschine implementiert. Es ist möglich, Methoden zu schreiben, so dass sie nicht in State-Maschinen kompiliert werden. Dies wird oft als Fast-Track-Async-Methode bezeichnet. Diese können wie folgt implementiert werden:

public Task DoSomethingAsync()
{
    return DoSomethingElseAsync();
}

Beim Benutzen Task.WhenAll Es ist möglich, diesen Fast-Track-Code beizubehalten und gleichzeitig sicherzustellen, dass der Anrufer warten kann, bis alle Aufgaben erledigt sind, z.

public Task DoSomethingAsync()
{
    var t1 = DoTaskAsync("t2.1", 3000);
    var t2 = DoTaskAsync("t2.2", 2000);
    var t3 = DoTaskAsync("t2.3", 1000);

    return Task.WhenAll(t1, t2, t3);
}

11
2017-12-24 16:25



Die anderen Antworten auf diese Frage bieten technische Gründe dafür await Task.WhenAll(t1, t2, t3); Ist bevorzugt. Diese Antwort zielt darauf ab, es von einer weicheren Seite zu betrachten (worauf @ usr anspielt), während es immer noch zur selben Schlussfolgerung kommt.

await Task.WhenAll(t1, t2, t3); ist ein mehr funktionaler Ansatz, da er Absicht erklärt und atomar ist.

Mit await t1; await t2; await t3;Es gibt nichts, was einen Teamkollegen (oder vielleicht sogar dein zukünftiges Selbst!) davon abhält, Code zwischen den Individuen hinzuzufügen await Aussagen. Sicher, Sie haben es auf eine Zeile komprimiert, um das im Wesentlichen zu erreichen, aber das löst das Problem nicht. Außerdem ist es in einer Teameinstellung generell eine schlechte Form, mehrere Anweisungen in eine bestimmte Codezeile aufzunehmen, da dadurch die Quelldatei für menschliche Augen schwerer zu scannen ist.

Einfach gesagt, await Task.WhenAll(t1, t2, t3); ist wartungsfreundlicher, da es Ihre Absicht deutlicher kommuniziert und weniger anfällig für merkwürdige Bugs ist, die aus wohlmeinenden Aktualisierungen des Codes stammen können, oder sogar einfach falsch zusammengeführt werden.


0
2018-01-06 00:11



(Disclaimer: Diese Antwort wurde von Ian Griffiths 'TPL Async Kurs übernommen Plural Sicht)

Ein weiterer Grund für WhenAll ist die Exception-Behandlung.

Angenommen, Sie hatten einen try-catch-Block für Ihre DoWork-Methoden und nehmen an, dass sie verschiedene DoTask-Methoden aufgerufen haben:

static async Task DoWork1() // modified with try-catch
{
    try
    {
        var t1 = DoTask1Async("t1.1", 3000);
        var t2 = DoTask2Async("t1.2", 2000);
        var t3 = DoTask3Async("t1.3", 1000);

        await t1; await t2; await t3;

        Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
    }
    catch (Exception x)
    {
        // ...
    }

}

Wenn in diesem Fall alle drei Aufgaben Ausnahmen auslösen, wird nur die erste abgefangen. Jede spätere Ausnahme wird verloren gehen. I.e. Wenn t2 und t3 eine Ausnahme auslöst, wird nur t2 eingefangen; usw. Die nachfolgenden Aufgaben Ausnahmen werden unbeobachtet bleiben.

Wo wie in der WhenAll - wenn einige oder alle der Aufgaben fehlerhaft sind, wird die resultierende Aufgabe alle Ausnahmen enthalten. Das await-Schlüsselwort gibt immer noch die erste Ausnahme zurück. Daher sind die anderen Ausnahmen immer noch praktisch unbeobachtet. Ein Weg, dies zu überwinden, ist, nach der Aufgabe WhenAll eine leere Fortsetzung hinzuzufügen und dort auf die Wartezeit zu warten. Wenn die Aufgabe fehlschlägt, wirft die Ergebniseigenschaft die vollständige Aggregatausnahme:

static async Task DoWork2() //modified to catch all exceptions
{
    try
    {
        var t1 = DoTask1Async("t1.1", 3000);
        var t2 = DoTask2Async("t1.2", 2000);
        var t3 = DoTask3Async("t1.3", 1000);

        var t = Task.WhenAll(t1, t2, t3);
        await t.ContinueWith(x => { });

        Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t.Result[0], t.Result[1], t.Result[2]));
    }
    catch (Exception x)
    {
        // ...
    }
}

0
2018-05-05 15:33