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?
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.
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.
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.
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.
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.
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
.
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.