Was ist der Unterschied zwischen Task.Start / Wait und Async / Await?

206

Ich vermisse vielleicht etwas, aber was ist der Unterschied zwischen:

public void MyMethod()
{
  Task t = Task.Factory.StartNew(DoSomethingThatTakesTime);
  t.Wait();
  UpdateLabelToSayItsComplete();
}

public async void MyMethod()
{
  var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
  await result;
  UpdateLabelToSayItsComplete();
}

private void DoSomethingThatTakesTime()
{
  Thread.Sleep(10000);
}
Jon
quelle

Antworten:

395

Mir fehlt vielleicht etwas

Du bist.

Was ist der Unterschied zwischen tun Task.Waitund await task?

Sie bestellen Ihr Mittagessen beim Kellner im Restaurant. Einen Moment nach der Bestellung kommt ein Freund herein, setzt sich neben Sie und beginnt ein Gespräch. Jetzt haben Sie zwei Möglichkeiten. Sie können Ihren Freund ignorieren, bis die Aufgabe erledigt ist - Sie können warten, bis Ihre Suppe eintrifft, und nichts anderes tun, während Sie warten. Oder Sie können Ihrem Freund antworten, und wenn Ihr Freund aufhört zu reden, bringt Ihnen der Kellner Ihre Suppe.

Task.Waitblockiert, bis die Aufgabe abgeschlossen ist - Sie ignorieren Ihren Freund, bis die Aufgabe abgeschlossen ist. awaitVerarbeitet weiterhin Nachrichten in der Nachrichtenwarteschlange. Wenn die Aufgabe abgeschlossen ist, wird eine Nachricht in die Warteschlange gestellt, die besagt, dass Sie dort weitermachen, wo Sie nach dem Warten aufgehört haben. Sie sprechen mit Ihrem Freund, und wenn das Gespräch unterbrochen wird, kommt die Suppe an.

Eric Lippert
quelle
5
@ronag Nein, das ist es nicht. Wie würde es Ihnen gefallen, wenn das Warten auf eine Task, die 10 ms dauert, tatsächlich eine 10-stündige Ausführung Taskin Ihrem Thread ausführen würde , wodurch Sie für die gesamten 10 Stunden blockiert würden?
Svick
62
@StrugglingCoder: Der await Betreiber nicht tut nichts außer seinen Operanden auswerten und dann sofort eine Aufgabe an den aktuellen Anrufer zurückzukehren . Die Leute haben die Idee im Kopf, dass Asynchronität nur durch Auslagern von Arbeit auf Threads erreicht werden kann, aber das ist falsch. Sie können das Frühstück kochen und die Zeitung lesen, während sich der Toast im Toaster befindet, ohne einen Koch zu beauftragen, der sich den Toaster ansieht. Die Leute sagen das gut, es muss ein Faden - ein Arbeiter - im Toaster versteckt sein, aber ich versichere Ihnen, dass, wenn Sie in Ihren Toaster schauen, kein kleiner Kerl da drin ist, der den Toast beobachtet.
Eric Lippert
11
@StrugglingCoder: Also, wer macht die Arbeit, die Sie fragen? Möglicherweise erledigt ein anderer Thread die Arbeit, und dieser Thread wurde einer CPU zugewiesen, sodass die Arbeit tatsächlich ausgeführt wird. Vielleicht wird die Arbeit von Hardware erledigt und es gibt überhaupt keinen Thread. Aber Sie sagen, es muss doch einen Thread in der Hardware geben . Nein. Hardware ist unterhalb der Thread-Ebene vorhanden. Es muss keinen Thread geben! Sie könnten davon profitieren, Stephen Clearys Artikel There Is No Thread zu lesen.
Eric Lippert
6
@StrugglingCoder: Nun, Frage, nehmen wir an, es wird asynchrone Arbeit ausgeführt und es gibt keine Hardware und es gibt keinen anderen Thread. Wie ist das möglich? Angenommen, das, worauf Sie gewartet haben, stellt eine Reihe von Windows-Nachrichten in die Warteschlange , von denen jede ein wenig Arbeit leistet. Was passiert nun? Wenn Sie die Steuerung an die Nachrichtenschleife zurückgeben, werden Nachrichten aus der Warteschlange gezogen, wobei jedes Mal ein wenig Arbeit geleistet wird. Der letzte Job, der ausgeführt wird, ist "Fortsetzung der Aufgabe ausführen". Kein zusätzlicher Thread!
Eric Lippert
8
@StrugglingCoder: Nun denke darüber nach, was ich gerade gesagt habe. Sie wissen bereits, dass Windows so funktioniert . Sie führen eine Reihe von Mausbewegungen und Tastenklicks aus und so weiter. Nachrichten werden in die Warteschlange gestellt und nacheinander verarbeitet. Jede Nachricht verursacht einen geringen Arbeitsaufwand, und wenn alles erledigt ist, fährt das System fort. Async in einem Thread ist nichts anderes als das, was Sie bereits gewohnt sind: große Aufgaben in kleine Teile aufteilen, in die Warteschlange stellen und alle kleinen Teile in einer bestimmten Reihenfolge ausführen. Einige dieser Hinrichtungen führen dazu, dass andere Arbeiten in die Warteschlange gestellt werden und das Leben weitergeht. Ein Thread!
Eric Lippert
121

Um Erics Antwort hier zu demonstrieren, gibt es einen Code:

public void ButtonClick(object sender, EventArgs e)
{
  Task t = new Task.Factory.StartNew(DoSomethingThatTakesTime);
  t.Wait();  
  //If you press Button2 now you won't see anything in the console 
  //until this task is complete and then the label will be updated!
  UpdateLabelToSayItsComplete();
}

public async void ButtonClick(object sender, EventArgs e)
{
  var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
  await result;
  //If you press Button2 now you will see stuff in the console and 
  //when the long method returns it will update the label!
  UpdateLabelToSayItsComplete();
}

public void Button_2_Click(object sender, EventArgs e)
{
  Console.WriteLine("Button 2 Clicked");
}

private void DoSomethingThatTakesTime()
{
  Thread.Sleep(10000);
}
Jon
quelle
27
+1 für den Code (es ist besser, einmal auszuführen als hundertmal zu lesen). Aber der Ausdruck " //If you press Button2 now you won't see anything in the console until this task is complete and then the label will be updated!" ist irreführend. Beim Drücken der Schaltfläche mit der t.Wait();Ereignisbehandlungsroutine für Schaltflächenklicks ButtonClick()ist es nicht möglich, etwas zu drücken und dann etwas in der Konsole anzuzeigen und die Beschriftung zu aktualisieren, "bis diese Aufgabe abgeschlossen ist", da die GUI eingefroren ist und nicht mehr reagiert, dh Klicks oder Interaktionen mit der GUI werden LOST bis zum Abschluss der Task - Warte
Gennady Vanin Геннадий Ванин
2
Ich denke, Eric geht davon aus, dass Sie ein grundlegendes Verständnis der Task-API haben. Ich schaue mir diesen Code an und sage mir: "Yup t.Waitwird den Haupt-Thread blockieren, bis die Aufgabe abgeschlossen ist."
Der Muffin-Mann
50

Dieses Beispiel zeigt den Unterschied sehr deutlich. Mit async / await blockiert der aufrufende Thread nicht und wird weiter ausgeführt.

static void Main(string[] args)
{
    WriteOutput("Program Begin");
    // DoAsTask();
    DoAsAsync();
    WriteOutput("Program End");
    Console.ReadLine();
}

static void DoAsTask()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
    WriteOutput("2 - Task started");
    t.Wait();
    WriteOutput("3 - Task completed with result: " + t.Result);
}

static async Task DoAsAsync()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
    WriteOutput("2 - Task started");
    var result = await t;
    WriteOutput("3 - Task completed with result: " + result);
}

static int DoSomethingThatTakesTime()
{
    WriteOutput("A - Started something");
    Thread.Sleep(1000);
    WriteOutput("B - Completed something");
    return 123;
}

static void WriteOutput(string message)
{
    Console.WriteLine("[{0}] {1}", Thread.CurrentThread.ManagedThreadId, message);
}

DoAsTask-Ausgabe:

[1] Programmbeginn
[1] 1 - Starten
[1] 2 - Aufgabe gestartet
[3] A - Etwas gestartet
[3] B - Etwas abgeschlossen
[1] 3 - Aufgabe mit Ergebnis abgeschlossen: 123
[1] Programmende

DoAsAsync-Ausgabe:

[1] Programmbeginn
[1] 1 - Starten
[1] 2 - Aufgabe gestartet
[3] A - Etwas gestartet
[1] Programmende
[3] B - Etwas abgeschlossen
[3] 3 - Aufgabe mit Ergebnis abgeschlossen: 123

Update: Verbessertes Beispiel durch Anzeigen der Thread-ID in der Ausgabe.

Mas
quelle
4
Aber wenn ich es tue: neue Aufgabe (DoAsTask) .Start (); anstelle von DoAsAsync (); Ich bekomme die gleiche Funktionalität, also wo ist der Vorteil des
Wartens
1
Bei Ihrem Vorschlag muss das Ergebnis der Aufgabe an einer anderen Stelle ausgewertet werden, z. B. bei einer anderen Methode oder einem Lambda. Das asynchrone Warten erleichtert das Verfolgen des asynchronen Codes. Es ist nur ein Syntaxverbesserer.
Mas
@Mas ich verstehe nicht, warum das Programmende nach A ist - etwas gestartet. Nach meinem Verständnis sollte das Abwarten des Keyword-Prozesses sofort zum Hauptkontext führen und dann zurückgehen.
@JimmyJimm Nach meinem Verständnis wird Task.Factory.StartNew einen neuen Thread starten, um DoSomethingThatTakesTime auszuführen. Daher gibt es keine Garantie dafür, ob Program End oder A - Started Something zuerst ausgeführt wird.
RiaanDP
@ JimmyJimm: Ich habe das Beispiel aktualisiert, um die Thread-IDs anzuzeigen. Wie Sie sehen können, werden "Programmede" und "A - Etwas gestartet" in verschiedenen Threads ausgeführt. Die Reihenfolge ist also eigentlich nicht deterministisch.
Mas
10

Wait () führt dazu, dass möglicherweise asynchroner Code synchron ausgeführt wird. Warten wird nicht.

Sie haben beispielsweise eine asp.net-Webanwendung. UserA ruft den Endpunkt / getUser / 1 auf. Der asp.net-App-Pool wählt einen Thread aus dem Thread-Pool (Thread1) aus und dieser Thread führt einen http-Aufruf durch. Wenn Sie Wait () ausführen, wird dieser Thread blockiert, bis der http-Aufruf aufgelöst wird. Wenn UserB / getUser / 2 aufruft, muss der App-Pool während des Wartens einen anderen Thread (Thread2) bereitstellen, um erneut einen http-Aufruf durchzuführen. Sie haben gerade einen anderen Thread ohne Grund erstellt (tatsächlich aus dem App-Pool abgerufen), da Sie Thread1 nicht verwenden können. Er wurde von Wait () blockiert.

Wenn Sie "Warten" für Thread1 verwenden, verwaltet SyncContext die Synchronisierung zwischen Thread1 und http-Aufruf. Es wird einfach benachrichtigt, sobald der http-Aufruf abgeschlossen ist. Wenn UserB / getUser / 2 aufruft, verwenden Sie Thread1 erneut, um einen http-Aufruf zu tätigen, da er veröffentlicht wurde, sobald er auf einen Treffer wartet. Dann kann eine andere Anfrage es noch weiter nutzen. Sobald der http-Aufruf abgeschlossen ist (Benutzer1 oder Benutzer2), kann Thread1 das Ergebnis abrufen und zum Anrufer (Client) zurückkehren. Thread1 wurde für mehrere Aufgaben verwendet.

Teoman Shipahi
quelle
9

In diesem Beispiel praktisch nicht viel. Wenn Sie auf eine Aufgabe warten, die für einen anderen Thread zurückgegeben wird (z. B. einen WCF-Aufruf) oder die Steuerung an das Betriebssystem abgibt (z. B. Datei-E / A), verbraucht wait weniger Systemressourcen, indem ein Thread nicht blockiert wird.

Foson
quelle
3

Im obigen Beispiel können Sie "TaskCreationOptions.HideScheduler" verwenden und die "DoAsTask" -Methode stark ändern. Die Methode selbst ist nicht asynchron, wie es bei "DoAsAsync" der Fall ist, da sie einen "Task" -Wert zurückgibt und als "async" markiert ist, wobei mehrere Kombinationen vorgenommen werden. Auf diese Weise erhalte ich genau das gleiche wie bei der Verwendung von "async / await". ::

static Task DoAsTask()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime, TaskCreationOptions.HideScheduler); //<-- HideScheduler do the magic

    TaskCompletionSource<int> tsc = new TaskCompletionSource<int>();
    t.ContinueWith(tsk => tsc.TrySetResult(tsk.Result)); //<-- Set the result to the created Task

    WriteOutput("2 - Task started");

    tsc.Task.ContinueWith(tsk => WriteOutput("3 - Task completed with result: " + tsk.Result)); //<-- Complete the Task
    return tsc.Task;
}
user8545699
quelle