Was ist der Unterschied zwischen der Rückgabe von void und der Rückgabe einer Aufgabe?

128

Beim Betrachten verschiedener C # Async CTP-Beispiele sehe ich einige Async-Funktionen, die zurückkehren void, und andere, die das Nicht-Generische zurückgeben Task. Ich kann sehen, warum die Rückgabe von a Task<MyType>nützlich ist, um Daten an den Aufrufer zurückzugeben, wenn der asynchrone Vorgang abgeschlossen ist, aber die Funktionen, die ich gesehen habe und die den Rückgabetyp haben, geben Taskniemals Daten zurück. Warum nicht zurückkehren void?

James Cadd
quelle

Antworten:

214

Die Antworten von SLaks und Killercam sind gut; Ich dachte, ich würde nur ein bisschen mehr Kontext hinzufügen.

Ihre erste Frage betrifft im Wesentlichen, welche Methoden markiert werden können async.

Eine Methode, die als zurückgegeben werden asynckann void, Taskoder Task<T>. Was sind die Unterschiede zwischen ihnen?

Eine Task<T>zurückkehrende asynchrone Methode kann erwartet werden, und wenn die Aufgabe abgeschlossen ist, wird ein T angeboten.

Eine Taskzurückkehrende asynchrone Methode kann abgewartet werden. Wenn die Aufgabe abgeschlossen ist, soll die Fortsetzung der Aufgabe ausgeführt werden.

Eine voidzurückkehrende asynchrone Methode kann nicht erwartet werden. Es ist eine "Feuer und Vergessen" -Methode. Es funktioniert asynchron und Sie können nicht sagen, wann es fertig ist. Das ist mehr als ein bisschen komisch; Wie SLaks sagt, würden Sie dies normalerweise nur tun, wenn Sie einen asynchronen Ereignishandler erstellen. Das Ereignis wird ausgelöst, der Handler wird ausgeführt. Niemand wird auf die vom Ereignishandler zurückgegebene Aufgabe "warten", da Ereignishandler keine Aufgaben zurückgeben, und selbst wenn dies der Fall wäre, welcher Code würde die Aufgabe für etwas verwenden? Normalerweise überträgt der Benutzercode die Kontrolle überhaupt nicht an den Handler.

Ihre zweite Frage in einem Kommentar betrifft im Wesentlichen das, was bearbeitet werden kann await:

Welche Arten von Methoden können awaitbearbeitet werden? Kann eine Methode zur Rückgabe von Leeren awaitbearbeitet werden?

Nein, eine Rückgabemethode kann nicht erwartet werden. Der Compiler übersetzt await M()in einen Aufruf von M().GetAwaiter(), wobei es sich GetAwaitermöglicherweise um eine Instanzmethode oder eine Erweiterungsmethode handelt. Der erwartete Wert muss einer sein, für den Sie einen Kellner erhalten können. Es ist klar, dass eine Methode zur Rückgabe von Leeren keinen Wert erzeugt, von dem Sie einen Kellner erhalten können.

Task-Rückgabemethoden können erwartete Werte erzeugen. Wir gehen davon aus, dass Dritte ihre eigenen Implementierungen von Taskähnlichen Objekten erstellen möchten, auf die gewartet werden kann, und dass Sie darauf warten können. Allerdings werden Sie nicht erlaubt werden , zu erklären , asyncalles Methoden , die Rückkehr aber void, Taskoder Task<T>.

(UPDATE: Mein letzter Satz dort wird möglicherweise durch eine zukünftige Version von C # verfälscht. Es gibt einen Vorschlag, andere Rückgabetypen als Aufgabentypen für asynchrone Methoden zuzulassen.)

(UPDATE: Die oben erwähnte Funktion hat es in C # 7 geschafft.)

Eric Lippert
quelle
7
+1 Ich denke, das einzige, was fehlt, ist der Unterschied in der Behandlung von Ausnahmen in ungültigen asynchronen Methoden.
João Angelo
10
@JamesCadd: Angenommen, eine asynchrone Arbeit löst eine Ausnahme aus. Wer fängt es? Der Code, der die asynchrone Task gestartet hat, befindet sich nicht mehr auf dem Stapel - möglicherweise nicht einmal auf demselben Thread - und Ausnahmen setzen voraus, dass sich alle catch / finally-Blöcke auf dem Stapel befinden . Also, was machst du? Wir speichern die Ausnahmeinformationen in der Aufgabe, damit Sie sie später überprüfen können. Wenn die Methode jedoch nichtig ist, steht dem Benutzercode keine Aufgabe zur Verfügung. Wie genau wir mit dieser Situation umgehen, war umstritten, und ich erinnere mich im Moment nicht daran, worüber wir uns entschieden haben.
Eric Lippert
8
Diese Frage habe ich Stephen Toub bei BUILD gestellt. In .NET 4.0 würden nicht beobachtete, nicht behandelte Ausnahmen in Aufgaben den Prozess schließlich zum Absturz bringen, sobald TPL feststellt, dass sie nicht beobachtet wurden. In 4.5 haben sie das Standardverhalten so geändert, dass nicht beobachtete Ausnahmen weiterhin über das TaskScheduler :: UnobservedTaskException-Ereignis gemeldet werden, der Prozess jedoch nicht mehr abstürzt. Wenn Sie das alte 4.0-Verhalten möchten, können Sie sich mit <runtime> <ThrowUnobservedTaskExceptions enabled = "true" /> </ runime> wieder anmelden. Höchstwahrscheinlich wurde die Änderung genau zur Unterstützung von Fire-and-Forget für nichtige asynchrone Methoden vorgenommen.
Drew Marsh
4
async voidMethoden lösen ihre Ausnahme für die aus SynchronizationContext, die zum Zeitpunkt der Ausführung aktiv war. Dies ähnelt dem Verhalten von (synchronen) Ereignishandlern. @DrewMarsh: Die UnobservedTaskExceptionEinstellung und zur Laufzeit gilt nur für asynchrone Task- Methoden "Feuer und Vergessen" , nicht für async voidMethoden.
Stephen Cleary
1
Zitierlink
Luke Puplett
23

Falls der Anrufer auf die Aufgabe warten oder eine Fortsetzung hinzufügen möchte.

Der einzige Grund für die Rückkehr voidist, wenn Sie nicht zurückkehren können, Taskweil Sie einen Ereignishandler schreiben.

SLaks
quelle
Ich dachte, es wäre möglich, Methoden abzuwarten, die auch einen leeren Typ zurückgeben - könnten Sie etwas näher darauf eingehen?
James Cadd
1
Nein, das kannst du nicht. Wenn die Methode zurückkehrt void, haben Sie keine Möglichkeit, an die von ihr generierte Aufgabe zu gelangen. (Eigentlich bin ich mir nicht sicher, ob es überhaupt eine erzeugt Task)
SLaks
18

Methoden, die zurückkehren Taskund zusammensetzbar Task<T>sind - das heißt, Sie können awaitsie innerhalb einer asyncMethode verwenden.

asyncRückgabemethoden voidsind nicht zusammensetzbar, haben jedoch zwei weitere wichtige Eigenschaften:

  1. Sie können als Ereignishandler verwendet werden.
  2. Sie repräsentieren eine asynchrone Operation der "obersten Ebene".

Der zweite Punkt ist wichtig, wenn Sie sich mit einem Kontext befassen, in dem die Anzahl der ausstehenden asynchronen Operationen beibehalten wird .

Der ASP.NET-Kontext ist ein solcher Kontext. Wenn Sie asynchrone TaskMethoden verwenden, ohne sie von einer asynchronen voidMethode abzuwarten , wird die ASP.NET-Anforderung zu früh abgeschlossen.

Ein weiterer Kontext ist der, den AsyncContextich für Unit-Tests geschrieben habe ( hier verfügbar ). Die AsyncContext.RunMethode verfolgt die Anzahl der ausstehenden Vorgänge und kehrt zurück, wenn sie Null ist.

Stephen Cleary
quelle
12

Typ Task<T>ist der Arbeitspferdetyp der Task Parallel Library (TPL). Er repräsentiert das Konzept von "einigen Arbeiten / Jobs, Tdie in Zukunft ein typisches Ergebnis liefern werden". Das Konzept "Arbeit, die in Zukunft abgeschlossen sein wird, aber kein Ergebnis liefert" wird durch den nicht generischen Aufgabentyp dargestellt.

Genau wie das Ergebnis des Typs Terzeugt wird, ist und Implementierungsdetail einer bestimmten Aufgabe; Die Arbeit wird möglicherweise an einen anderen Prozess auf dem lokalen Computer, an einen anderen Thread usw. ausgelagert. TPL-Aufgaben werden im aktuellen Prozess normalerweise an Arbeitsthreads aus einem Thread-Pool ausgelagert, aber dieses Implementierungsdetail ist für den Task<T>Typ nicht grundlegend . Vielmehr Task<T>kann a jede Operation mit hoher Latenz darstellen, die a erzeugt T.

Basierend auf Ihrem Kommentar oben:

Der awaitAusdruck bedeutet "diesen Ausdruck auswerten, um ein Objekt zu erhalten, das eine Arbeit darstellt, die in Zukunft ein Ergebnis hervorbringen wird. Melden Sie den Rest der aktuellen Methode als Rückruf an, der mit der Fortsetzung dieser Aufgabe verbunden ist. Sobald diese Aufgabe erstellt wurde und der Rückruf erfolgt." ist angemeldet, sofort die Kontrolle an meinen Anrufer zurückgeben ". Dies steht im Gegensatz zu einem regulären Methodenaufruf, der bedeutet: "Denken Sie daran, was Sie tun, führen Sie diese Methode aus, bis sie vollständig abgeschlossen ist, und setzen Sie sie dort fort, wo Sie aufgehört haben, und kennen Sie nun das Ergebnis der Methode."


Bearbeiten: Ich sollte Eric Lipperts Artikel im Oktober 2011 im MSDN Magazine zitieren, da dies eine große Hilfe für mich war, um dieses Zeug überhaupt zu verstehen.

Weitere Informationen und Whitepages finden Sie hier .

Ich hoffe, das ist hilfreich.

Mond Ritter
quelle