web-dev-qa-db-de.com

Warten auf mehrere Aufgaben mit unterschiedlichen Ergebnissen

Ich habe 3 Aufgaben:

private async Task<Cat> FeedCat() {}
private async Task<House> SellHouse() {}
private async Task<Tesla> BuyCar() {}

Sie müssen alle ausgeführt werden, bevor mein Code fortgesetzt werden kann, und ich benötige auch die Ergebnisse von beiden. Keines der Ergebnisse hat etwas miteinander gemeinsam

Wie rufe ich an und warte, bis die drei Aufgaben abgeschlossen sind und die Ergebnisse vorliegen?

191

Nachdem Sie WhenAll verwendet haben, können Sie die Ergebnisse einzeln mit await abrufen:

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

await Task.WhenAll(catTask, houseTask, carTask);

var cat = await catTask;
var house = await houseTask;
var car = await carTask;

Sie können auch Task.Result (da Sie zu diesem Zeitpunkt wissen, dass alle erfolgreich abgeschlossen wurden). Ich empfehle jedoch die Verwendung von await, da dies eindeutig korrekt ist, während Result in anderen Szenarien Probleme verursachen kann.

320
Stephen Cleary

Einfach await die drei Aufgaben getrennt, nachdem Sie alle gestartet haben.

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

var cat = await catTask;
var house = await houseTask;
var car = await carTask;
74
Servy

Wenn Sie C # 7 verwenden, können Sie eine praktische Wrapper-Methode wie diese verwenden ...

public static class TaskEx
{
    public static async Task<(T1, T2)> WhenAll<T1, T2>(Task<T1> task1, Task<T2> task2)
    {
        await Task.WhenAll(task1, task2);
        return (task1.Result, task2.Result);
    }
}

... um eine bequeme Syntax wie diese zu aktivieren, wenn Sie auf mehrere Tasks mit unterschiedlichen Rückgabetypen warten möchten. Natürlich müssten Sie mehrere Überladungen vornehmen, damit die Anzahl der Aufgaben unterschiedlich hoch ist.

var (someInt, someString) = await TaskEx.WhenAll(GetIntAsync(), GetStringAsync());
30
Joel Mueller

Sie können sie in Aufgaben speichern und dann auf alle warten:

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

await Task.WhenAll(catTask, houseTask, carTask);

Cat cat = await catTask;
House house = await houseTask;
Car car = await carTask;
11
Reed Copsey

Bei drei Aufgaben - FeedCat(), SellHouse() und BuyCar() - gibt es zwei interessante Fälle: Entweder werden sie alle synchron ausgeführt (aus irgendeinem Grund, vielleicht durch Caching oder einen Fehler). oder nicht.

Nehmen wir an, wir haben aus der Frage:

Task<string> DoTheThings() {
    Task<Cat> x = FeedCat();
    Task<House> y = SellHouse();
    Task<Tesla> z = BuyCar();
    // what here?
}

Ein einfacher Ansatz wäre nun:

Task.WhenAll(x, y, z);

aber ... das ist nicht bequem für die Verarbeitung der Ergebnisse; Normalerweise möchten wir await, dass:

async Task<string> DoTheThings() {
    Task<Cat> x = FeedCat();
    Task<House> y = SellHouse();
    Task<Tesla> z = BuyCar();

    await Task.WhenAll(x, y, z);
    // presumably we want to do something with the results...
    return DoWhatever(x.Result, y.Result, z.Result);
}

dies verursacht jedoch viel Overhead und weist verschiedene Arrays (einschließlich des Arrays params Task[]) und Listen (intern) zu. Es funktioniert, aber es ist nicht großartig, IMO. In vielerlei Hinsicht ist es einfacher, jeweils eine async -Operation und nur await zu verwenden:

async Task<string> DoTheThings() {
    Task<Cat> x = FeedCat();
    Task<House> y = SellHouse();
    Task<Tesla> z = BuyCar();

    // do something with the results...
    return DoWhatever(await x, await y, await z);
}

Im Gegensatz zu einigen der obigen Kommentare hat die Verwendung von await anstelle von Task.WhenAllkein Unterschied Einfluss darauf, wie die Tasks ausgeführt werden (gleichzeitig, nacheinander usw.). Auf der höchsten Ebene war Task.WhenAllvordatiert eine gute Compiler-Unterstützung für async/await und war nützlich , wenn diese Dinge existierten nicht . Es ist auch nützlich, wenn Sie eine beliebige Reihe von Aufgaben haben, anstatt 3 diskrete Aufgaben.

Aber: Wir haben immer noch das Problem, dass async/await eine Menge Compiler-Rauschen für die Fortsetzung erzeugt. Wenn es wahrscheinlich ist, dass die Tasks tatsächlich synchron abgeschlossen werden , können wir dies optimieren, indem wir einen synchronen Pfad mit einem asynchronen Fallback einbauen:

Task<string> DoTheThings() {
    Task<Cat> x = FeedCat();
    Task<House> y = SellHouse();
    Task<Tesla> z = BuyCar();

    if(x.Status == TaskStatus.RanToCompletion &&
       y.Status == TaskStatus.RanToCompletion &&
       z.Status == TaskStatus.RanToCompletion)
        return Task.FromResult(
          DoWhatever(a.Result, b.Result, c.Result));
       // we can safely access .Result, as they are known
       // to be ran-to-completion

    return Awaited(x, y, z);
}

async Task Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
    return DoWhatever(await x, await y, await z);
}

Dieser "Sync Path with Async Fallback" -Ansatz wird immer häufiger verwendet, insbesondere bei Hochleistungscode, bei dem synchrone Abschlüsse relativ häufig sind. Beachten Sie, dass es überhaupt nicht hilft, wenn die Fertigstellung immer wirklich asynchron ist.

Zusätzliche Dinge, die hier zutreffen:

  1. in C # der letzten Zeit ist ein allgemeines Muster für die Fallback-Methode async allgemein als lokale Funktion implementiert:

    Task<string> DoTheThings() {
        async Task<string> Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
            return DoWhatever(await a, await b, await c);
        }
        Task<Cat> x = FeedCat();
        Task<House> y = SellHouse();
        Task<Tesla> z = BuyCar();
    
        if(x.Status == TaskStatus.RanToCompletion &&
           y.Status == TaskStatus.RanToCompletion &&
           z.Status == TaskStatus.RanToCompletion)
            return Task.FromResult(
              DoWhatever(a.Result, b.Result, c.Result));
           // we can safely access .Result, as they are known
           // to be ran-to-completion
    
        return Awaited(x, y, z);
    }
    
  2. bevorzuge ValueTask<T> gegenüber Task<T>, wenn es eine gute Chance gibt, dass Dinge jemals völlig synchron mit vielen verschiedenen Rückgabewerten ablaufen:

    ValueTask<string> DoTheThings() {
        async ValueTask<string> Awaited(ValueTask<Cat> a, Task<House> b, Task<Tesla> c) {
            return DoWhatever(await a, await b, await c);
        }
        ValueTask<Cat> x = FeedCat();
        ValueTask<House> y = SellHouse();
        ValueTask<Tesla> z = BuyCar();
    
        if(x.IsCompletedSuccessfully &&
           y.IsCompletedSuccessfully &&
           z.IsCompletedSuccessfully)
            return new ValueTask<string>(
              DoWhatever(a.Result, b.Result, c.Result));
           // we can safely access .Result, as they are known
           // to be ran-to-completion
    
        return Awaited(x, y, z);
    }
    
  3. wenn möglich, ziehe IsCompletedSuccessfullyStatus == TaskStatus.RanToCompletion vor; Dies ist jetzt in .NET Core für Task und überall für ValueTask<T> vorhanden.

10
Marc Gravell

Wenn Sie versuchen, alle Fehler zu protokollieren, stellen Sie sicher, dass Sie die Task.WhenAll-Zeile in Ihrem Code beibehalten. Viele Kommentare lassen darauf schließen, dass Sie sie entfernen und auf einzelne Tasks warten können. Task.WhenAll ist wirklich wichtig für die Fehlerbehandlung. Ohne diese Zeile können Sie Ihren Code möglicherweise für unbeobachtete Ausnahmen offen lassen.

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

await Task.WhenAll(catTask, houseTask, carTask);

var cat = await catTask;
var house = await houseTask;
var car = await carTask;

Stellen Sie sich vor, FeedCat löst eine Ausnahme im folgenden Code aus:

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

var cat = await catTask;
var house = await houseTask;
var car = await carTask;

In diesem Fall werden Sie niemals auf houseTask oder carTask warten. Hier gibt es 3 mögliche Szenarien:

  1. SellHouse ist bereits erfolgreich abgeschlossen, wenn FeedCat fehlgeschlagen ist. In diesem Fall geht es dir gut.

  2. SellHouse ist nicht vollständig und schlägt mit Ausnahme irgendwann fehl. Die Ausnahme wird nicht beachtet und im Finalizer-Thread erneut ausgelöst.

  3. SellHouse ist nicht vollständig und enthält die darin enthaltenen Wartezeiten. Falls Ihr Code in ASP.NET SellHouse ausgeführt wird, schlägt dies fehl, sobald einige der Wartezeiten darin abgeschlossen sind. Dies geschieht, weil Sie im Grunde genommen einen Fire & Forget-Aufruf ausgelöst haben und der Synchronisierungskontext verloren ging, sobald FeedCat fehlschlug.

Hier ist der Fehler, den Sie für den Fall (3) erhalten:

System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. ---> System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
   at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
   at System.Web.HttpApplication.System.Web.Util.ISyncContext.Enter()
   at System.Web.Util.SynchronizationHelper.SafeWrapCallback(Action action)
   at System.Threading.Tasks.Task.Execute()
   --- End of inner exception stack trace ---
---> (Inner Exception #0) System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
   at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
   at System.Web.HttpApplication.System.Web.Util.ISyncContext.Enter()
   at System.Web.Util.SynchronizationHelper.SafeWrapCallback(Action action)
   at System.Threading.Tasks.Task.Execute()<---

In Fall (2) erhalten Sie einen ähnlichen Fehler, jedoch mit dem ursprünglichen Ausnahmestapel-Trace.

In .NET 4.0 und höher können Sie mit TaskScheduler.UnobservedTaskException nicht beobachtete Ausnahmen abfangen. Für .NET 4.5 und höher werden nicht beobachtete Ausnahmen standardmäßig verschluckt, für .NET 4.0 führt eine nicht beobachtete Ausnahme zum Absturz Ihres Prozesses.

Weitere Details hier: Task Exception Handling in .NET 4.5

3
samfromlv

Sie können Task.WhenAll wie erwähnt, oder Task.WaitAll, abhängig davon, ob der Thread warten soll. Schauen Sie sich den Link an, um eine Erklärung für beides zu erhalten.

WaitAll vs WhenAll

2
christiandev

Verwenden Task.WhenAll und dann warten Sie auf die Ergebnisse:

var tCat = FeedCat();
var tHouse = SellHouse();
var tCar = BuyCar();
await Task.WhenAll(tCat, tHouse, tCar);
Cat cat = await tCat;
House house = await tHouse;
Tesla car = await tCar; 
//as they have all definitely finished, you could also use Task.Value.
2
It'sNotALie.
var dn = await Task.WhenAll<dynamic>(FeedCat(),SellHouse(),BuyCar());

wenn Sie auf Cat zugreifen möchten, gehen Sie wie folgt vor:

var ct = (Cat)dn[0];

Dies ist sehr einfach zu bewerkstelligen und sehr nützlich, da keine komplexe Lösung angestrebt werden muss.

1
taurius

Vorwarnung

Nur ein kurzer Überblick für die Besucher dieses und anderer ähnlicher Threads, die nach einer Möglichkeit suchen, EntityFramework mithilfe von async + await + task tool-set zu parallelisieren: Das hier gezeigte Muster ist jedoch einwandfrei Für die spezielle Schneeflocke von EF wird keine parallele Ausführung ausgeführt, es sei denn, Sie verwenden in jedem beteiligten * Async () - Aufruf eine separate (neue) Datenbank-Kontext-Instanz.

Dies ist aufgrund der inhärenten Designeinschränkungen von ef-db-Kontexten erforderlich, die die parallele Ausführung mehrerer Abfragen in derselben ef-db-Kontextinstanz verbieten.


Indem Sie die bereits gegebenen Antworten nutzen, stellen Sie sicher, dass Sie alle Werte erfassen, auch wenn eine oder mehrere der Aufgaben zu einer Ausnahme führen:

  public async Task<string> Foobar() {
    async Task<string> Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
        return DoSomething(await a, await b, await c);
    }

    using (var carTask = BuyCarAsync())
    using (var catTask = FeedCatAsync())
    using (var houseTask = SellHouseAsync())
    {
        if (carTask.Status == TaskStatus.RanToCompletion //triple
            && catTask.Status == TaskStatus.RanToCompletion //cache
            && houseTask.Status == TaskStatus.RanToCompletion) { //hits
            return Task.FromResult(DoSomething(catTask.Result, carTask.Result, houseTask.Result)); //fast-track
        }

        cat = await catTask;
        car = await carTask;
        house = await houseTask;
        //or Task.AwaitAll(carTask, catTask, houseTask);
        //or await Task.WhenAll(carTask, catTask, houseTask);
        //it depends on how you like exception handling better

        return Awaited(catTask, carTask, houseTask);
   }
 }

Eine alternative Implementierung, die mehr oder weniger dieselben Leistungsmerkmale aufweist, könnte sein:

 public async Task<string> Foobar() {
    using (var carTask = BuyCarAsync())
    using (var catTask = FeedCatAsync())
    using (var houseTask = SellHouseAsync())
    {
        cat = catTask.Status == TaskStatus.RanToCompletion ? catTask.Result : (await catTask);
        car = carTask.Status == TaskStatus.RanToCompletion ? carTask.Result : (await carTask);
        house = houseTask.Status == TaskStatus.RanToCompletion ? houseTask.Result : (await houseTask);

        return DoSomething(cat, car, house);
     }
 }
0
XDS