2016-04-18 3 views
3

In diesem Code:Welcher Code wird tatsächlich "multi-threaded" in async/await-Muster ausgeführt?

public async Task v_task() 
{ 
    await Task.Run(() => Console.WriteLine("Hello!")); 
} 

public async void v1() 
{ 
    await v_task(); 
    // some other actions... 
} 

public void ButtonClick() 
{ 
    v1(); 

    Console.WriteLine("Hi!"); 
} 

Welche obigen Verfahren tatsächlich parallel ausgeführt werden, in dem Asynchron/await unteren Thread-Pool generiert, wenn Button genannt wird?

Ich meine, was sollte meine Bedenken über Race Conditions mit async arbeiten/erwarten? Alle Async-Methoden müssen im Thread des gleichen Aufrufers ausgeführt werden. Sollte ich Mutex für einen möglichen gemeinsamen Status verwenden? Wenn ja, wie könnte ich feststellen, was die gemeinsam genutzten Zustandsobjekte sind?

+0

Standardmäßig werden alle asynchronen Fortsetzungen im selben Synchronisierungskontext ausgeführt, den sie gestartet haben. In der Konsolenanwendung gibt es keinen Synchronisierungskontext und alle Aufgaben werden in einem Thread-Pool ausgeführt. Also ja, Sie sollten sich der Rennbedingungen bewusst sein. Gemeinsame Statusobjekte sind die Objekte, auf die in allen Aufgaben zugegriffen wird. – VMAtm

+0

Hallo @VMAtm! Also wird das Aufrufdiagramm im selben Synchronisationskontext ausgeführt (ich denke, dies ist der gleiche wie ein Thread), bis es das erste "Warte" -Schlüsselwort findet? An diesem Punkt (Ausführung des ersten "Wartens" im Aufrufdiagramm) wird der Code unter "Warten" in einem anderen Thread ausgeführt. – ptr0x

Antwort

6

Welche der oben genannten Methoden werden parallel im async/away generiert, wenn ButtonClick aufgerufen wird?

Nur die innerhalb der Task.Run.

Ich meine, was sollte meine Bedenken über Race Conditions mit async arbeiten/erwarten?

Ich schlage vor, Sie beginnen mit meinem async intro lesen, die erklärt, wie await tatsächlich funktioniert.

Zusammenfassend, async Methoden werden in der Regel in einer seriell asynchronen Mode geschrieben. Nehmen Sie diesen Code zum Beispiel:

CodeBeforeAwait(); 
await SomeOtherMethodAsync(); 
CodeAfterAwait(); 

Sie immer sagen können, dass CodeBeforeAwait ersten bis zum Abschluss ausgeführt wird, dann SomeOtherMethodAsync aufgerufen.Dann wartet unsere Methode (asynchron) auf SomeOtherMethodAsync, um abzuschließen, und erst danach wird CodeAfterAwait aufgerufen.

So ist es seriell asynchron. Es wird seriell ausgeführt, so wie Sie es erwarten, aber auch mit einem asynchronen Punkt in diesem Fluss (await).

Nun, sie kann nicht sagen, dassCodeBeforeAwait und CodeAfterAwait im selben Thread ausgeführt werden, zumindest nicht ohne mehr Kontext. await wird standardmäßig im aktuellen SynchronizationContext (oder dem aktuellen TaskScheduler, wenn es kein SyncCtx gibt) fortgesetzt. Wenn also die obige Beispielmethode im UI-Thread ausgeführt wurde, wissen Sie, dass CodeBeforeAwait und CodeAfterAwait beide im UI-Thread ausgeführt werden. Wenn es jedoch ohne einen Kontext ausgeführt wurde (d. H. Von einem Hintergrundthread oder einem Console-Hauptthread), kann CodeAfterAwait in einem anderen Thread ausgeführt werden.

Beachten Sie, dass selbst wenn Teile einer Methode in einem anderen Thread ausgeführt werden, die Runtime dafür sorgt, dass Barrieren gesetzt werden, bevor die Methode fortgesetzt wird.

Beachten Sie auch, dass Ihr ursprüngliches Beispiel Task.Run verwendet, die explizit Arbeit auf dem Thread-Pool platziert. Das ist ganz anders als async/await, und Sie werden das Multithread definitiv behandeln müssen.

Sollte ich Mutex auf möglichen gemeinsamen Zustand verwenden?

Ja. Wenn Ihr Code beispielsweise Task.Run verwendet, müssen Sie dies als separaten Thread behandeln. (Beachten Sie, dass es mit awaitLos einfacher ist, den Status überhaupt nicht mit anderen Threads zu teilen - wenn Sie Ihre Hintergrundaufgaben sauber halten können, sind sie viel einfacher zu arbeiten).

Wenn ja, wie kann ich feststellen, was die gemeinsam genutzten Statusobjekte sind?

Gleiche Antwort wie bei jeder anderen Art von Multi-Thread-Code: Code-Inspektion.

+0

Hey Stephen, danke für deine sehr nette Antwort! Also, zusammenfassend: Alle in einem Call-Graphen eingehenden Calls werden im selben Thread ausgeführt; Der Code wird nur in einem anderen Hintergrund-Thread ausgeführt, wenn ich explizit eine Aufgabe über Task.Run oder TaskCompletionSource erstelle? Mit anderen Worten, wenn ich keine Methode implementiere, die über Task.Run ausgeführt wird, wird mein gesamter Code im selben Thread ausgeführt? Mann, wenn die Antwort für die letzte Frage ja ist: das ist sehr schön! Vielen Dank, ich werde jetzt Ihren Artikel lesen. – ptr0x

+1

@ ptr0x: Nicht ganz; Es wird im selben * Kontext ausgeführt. Wenn Sie nur einen UI-Kontext verwenden, dann hat das die gleiche Bedeutung wie "gleicher Thread".Aber wenn Sie denselben Code in einer Konsolenanwendung ausführen, dann können es andere Threads sein. Und wenn Ihr Code das Verhalten der Standardkontextaufnahme (d. H. "ConfigureAwait (false)") explizit überschreibt, kann es den Kontext verwenden oder auf einem Thread-Pool-Thread ausgeführt werden. –

+0

Bekam es. Ich muss mich also nur um mögliche Race Conditions kümmern, wenn ich denselben Code explizit in verschiedenen Threads abspiele oder eine Aufgabe über Task.Run initiiere (was die Aufgabe in einem Thread-Pool-Thread ausführt), richtig? – ptr0x

2

Wenn Sie eine asynchrone Funktion aufrufen, wird Ihr Thread diese Funktion ausführen, bis eine Wartezeit erreicht wird.

Wenn Sie async-await nicht verwenden, wird der Thread verarbeitet, bis der erwartete Code beendet ist, und mit der Anweisung nach dem await fortfahren.

Aber wie Sie async-await verwenden, haben Sie dem Compiler gesagt, dass, wenn der Thread auf etwas warten muss, Sie andere Dinge tun können, anstatt zu warten. Der Thread wird diese anderen Dinge tun, bis Sie sagen: Jetzt warte bis deine ursprüngliche Sache beendet ist.

Aufgrund des Aufrufs einer asynchronen Funktion sind wir sicher, dass irgendwo drinnen ein warten sollte. Beachten Sie, dass Sie eine Compiler-Warnung erhalten, dass die Funktion synchron ausgeführt wird, wenn Sie eine asynchrone Funktion aufrufen, die nicht wartet.

Beispiel:

private async void OnButton1_clickec(object sender, ...) 
{ 
    string dataToSave = ...; 
    var saveTask = this.MyOpenFile.SaveAsync(dataToSave); 

    // the thread will go into the SaveAsync function and will 
    // do all things until it sees an await. 
    // because of the async SaveAsync we know there is somewhere an await 
    // As you didn't say await this.MyOpenfile.SaveAsync 
    // the thread will not wait but continue with the following 
    // statements: 

    DoSomethingElse() 
    await saveTask; 

    // here we see the await. The thread was doing something else, 
    // finished it, and now we say: await. That means it waits until its 
    // internal await is finished and continues with the statements after 
    // this internal await. 

Beachten Sie, dass, auch wenn die await irgendwo in SaveAsync fertig war, wird der Faden nicht die nächste Anweisung ausführen, bis Sie SaveTask erwarten. Dies hat zur Folge, dass DoSomethingElse nicht unterbrochen wird, wenn das Warten auf SaveAsync abgeschlossen ist.

Daher ist es normalerweise nicht sinnvoll, eine asynchrone Funktion zu erstellen, die weder einen Task noch eine Task zurückgibt < TResult> Die einzige Ausnahme ist ein Event-Handler. Die GUI muss nicht warten, bis der Event-Handler fertig ist.

+0

Vielen Dank Harald! Ich werde die Antwort von Stephen oben als Antwort akzeptieren, nur weil seine Antwort ein bisschen vollständiger ist. – ptr0x

Verwandte Themen