it-swarm.com.de

Was ist der Zweck von "Rückkehr erwarten" in C #?

Gibt es irgendein Szenario, bei dem die Schreibmethode folgendermaßen aussieht:

public async Task<SomeResult> DoSomethingAsync()
{
    // Some synchronous code might or might not be here... //
    return await DoAnotherThingAsync();
}

an Stelle von:

public Task<SomeResult> DoSomethingAsync()
{
    // Some synchronous code might or might not be here... //
    return DoAnotherThingAsync();
}

würde einen Sinn machen

Warum sollte return await construct verwendet werden, wenn Sie Task<T> direkt aus dem inneren Aufruf von DoAnotherThingAsync() zurückgeben können?

Ich sehe Code mit return await an so vielen Stellen, ich glaube, ich hätte etwas verpassen sollen. Soweit ich es verstehe, wäre das Verwenden von async/await-Keywords in diesem Fall und das direkte Zurückgeben der Task funktional gleichwertig. Warum zusätzlichen Overhead zusätzlicher await-Ebene hinzufügen?

182
TX_

Es gibt einen hinterlistigen Fall, wenn sich return in der normalen Methode und return await in der async-Methode anders verhalten: in Kombination mit using (oder allgemeiner beliebigem return await in einem try-Block).

Betrachten Sie diese zwei Versionen einer Methode:

Task<SomeResult> DoSomethingAsync()
{
    using (var foo = new Foo())
    {
        return foo.DoAnotherThingAsync();
    }
}

async Task<SomeResult> DoSomethingAsync()
{
    using (var foo = new Foo())
    {
        return await foo.DoAnotherThingAsync();
    }
}

Die erste Methode Dispose() das Foo-Objekt, sobald die DoAnotherThingAsync()-Methode zurückkehrt. Dies ist wahrscheinlich lange bevor sie abgeschlossen ist. Dies bedeutet, dass die erste Version wahrscheinlich fehlerhaft ist (weil Foo zu früh verworfen wird), während die zweite Version gut funktioniert.

142
svick

Wenn Sie keine async benötigen (d. H. Sie können die Task direkt zurückgeben), verwenden Sie async nicht.

Es gibt einige Situationen, in denen return await nützlich ist, wenn Sie zum Beispiel zwei asynchrone Operationen ausführen müssen:

var intermediate = await FirstAsync();
return await SecondAwait(intermediate);

Weitere Informationen zur async-Leistung finden Sie in Stephen Toubs MSDN-Artikel und Video zum Thema.

Update: Ich habe ein blog post geschrieben, das viel detaillierter geht.

72
Stephen Cleary

Der einzige Grund, aus dem Sie dies tun möchten, ist, wenn der vorherige Code eine andere await enthält oder wenn Sie das Ergebnis auf irgendeine Weise manipulieren, bevor Sie es zurückgeben. Ein anderer Weg, auf dem dies geschehen könnte, ist ein try/catch, der die Behandlung von Ausnahmen ändert. Wenn Sie dies nicht tun, haben Sie Recht, es gibt keinen Grund, den Aufwand für die Erstellung der Methode async hinzuzufügen.

23
Servy

Ein weiterer Fall, den Sie möglicherweise warten müssen, ist der folgende:

async Task<IFoo> GetIFooAsync()
{
    return await GetFooAsync();
}

async Task<Foo> GetFooAsync()
{
    var foo = await CreateFooAsync();
    await foo.InitializeAsync();
    return foo;
}

In diesem Fall muss GetIFooAsync() auf das Ergebnis von GetFooAsync warten, da sich der Typ von T zwischen den beiden Methoden unterscheidet und Task<Foo> nicht Task<IFoo> direkt zugeordnet werden kann. Wenn Sie jedoch auf das Ergebnis warten, wird es zu Foo, die ist direkt IFoo zuweisbar. Dann verpackt die async-Methode das Ergebnis einfach in Task<IFoo> und schon geht es los.

12
Andrew Arnott

Wenn Sie die ansonsten einfache "Thunk" -Methode asynchron machen, wird eine asynchrone Zustandsmaschine im Arbeitsspeicher erstellt, während die nicht asynchrone Methode dies nicht tut. Obwohl dies oft darauf hinweisen kann, dass die nicht asynchrone Version verwendet wird, weil sie effizienter ist (was wahr ist), bedeutet dies auch, dass Sie im Fall eines Hang nicht über eine Beweise verfügen, dass diese Methode im "return/continuation stack" enthalten ist. Das macht es manchmal schwieriger, den Hang zu verstehen. 

Also ja, wenn perf nicht kritisch ist (und dies normalerweise nicht ist), werfe ich all diese Thunk-Methoden async aus, so dass ich die async-state-Maschine habe, die mir hilft, Hängen später zu diagnostizieren, und auch dazu beiträgt, dass dies der Fall ist Thunk-Methoden entwickeln sich im Laufe der Zeit immer weiter und geben fehlerhafte Aufgaben statt Wurf zurück.

3
Andrew Arnott

Das verwirrt mich auch und ich habe das Gefühl, dass die vorherigen Antworten Ihre eigentliche Frage übersehen haben:

Warum sollte das Konstrukt return await verwendet werden, wenn Sie Task direkt aus dem inneren Aufruf von DoAnotherThingAsync () zurückgeben können?

Nun, manchmal möchten Sie tatsächlich einen Task<SomeType>, Aber die meiste Zeit möchten Sie tatsächlich eine Instanz von SomeType, dh das Ergebnis der Aufgabe.

Aus deinem Code:

async Task<SomeResult> DoSomethingAsync()
{
    using (var foo = new Foo())
    {
        return await foo.DoAnotherThingAsync();
    }
}

Eine Person, die mit der Syntax nicht vertraut ist (z. B. ich), könnte denken, dass diese Methode einen Task<SomeResult> Zurückgeben sollte. Da sie jedoch mit async gekennzeichnet ist, bedeutet dies, dass der tatsächliche Rückgabetyp SomeResult ist. Wenn Sie nur return foo.DoAnotherThingAsync() verwenden, geben Sie eine Task zurück, die nicht kompiliert werden konnte. Der richtige Weg ist, das Ergebnis der Aufgabe zurückzugeben, also den return await.

2
heltonbiker

Wenn Sie return wait nicht verwenden, können Sie die Stack-Ablaufverfolgung beim Debuggen oder beim Ausdruck in den Protokollen bei Ausnahmen ruinieren.

Wenn Sie die Task zurückgeben, hat die Methode ihren Zweck erfüllt und befindet sich nicht mehr im Aufrufstapel. Wenn Sie return await verwenden, belassen Sie sie im Aufrufstapel.

Zum Beispiel:

Aufrufstack bei Verwendung von waitit: A erwartet die Task von B => B und wartet auf die Task von C

Aufrufstack, wenn nicht mit waitit: A erwartet die Aufgabe von C, die B zurückgegeben hat.

0
haimb