ConfigureAwait gibt die Fortsetzung an einen Pool-Thread weiter

8

Hier ist ein WinForms-Code:

%Vor%

Die Ausgabe:

%Vor%

Warum schiebt ConfigureAwait die await -Verlängerung proaktiv auf einen Pool-Thread?

Die MSDN-Dokumentation sagen:

  

continueOnCapturedContext ... true, um den Marshalling zu versuchen   Fortsetzung zurück zum ursprünglichen Kontext erfasst; sonst, falsch.

Ich verstehe, dass WinFormsSynchronizationContext auf dem aktuellen Thread installiert ist. Dennoch gibt es keinen Versuch zu marshalen , der Ausführungspunkt ist bereits da.

Somit ist es eher wie "fahre nie mit dem ursprünglichen Kontext fort, der aufgenommen wurde" ...

Wie erwartet gibt es keinen Threadwechsel, wenn der Ausführungspunkt bereits in einem Poolthread ohne Synchronisationskontext ist:

%Vor% %Vor%

Ich bin dabei, mir die Implementierung von ConfiguredTaskAwaitable für die Antworten.

Aktualisiert , ein weiterer Test, um zu sehen, ob any synchronisiert wird. Kontext ist nicht gut genug für die Fortsetzung (anstatt der ursprünglichen). Dies ist in der Tat der Fall:

%Vor% %Vor%     
Noseratio 26.03.2014, 21:02
quelle

3 Antworten

11
  

Warum konfiguriert ConfigureAwait pro-aktiv die Warte-Fortsetzung auf einen Pool-Thread hier?

Es wird nicht "zu einem Thread-Pool-Thread" gepusht, sondern "Zwinge mich nicht dazu, zum vorherigen SynchronizationContext zurückzukehren".

Wenn Sie den vorhandenen Kontext nicht erfassen, wird die Fortsetzung, die den Code nach dem await behandelt, nur auf einem Thread-Pool-Thread ausgeführt, da kein Kontext für die Rückübertragung vorhanden ist.

Nun, dies ist subtil anders als "push to a thread pool", da es keine Garantie gibt, dass es in einem Thread-Pool ausgeführt wird, wenn Sie ConfigureAwait(false) ausführen. Wenn Sie anrufen:

%Vor%

Es ist möglich, dass FooAsync() synchron ausgeführt wird. In diesem Fall verlassen Sie niemals den aktuellen Kontext. In diesem Fall hat ConfigureAwait(false) keinen wirklichen Effekt, da die vom Feature await erstellte Zustandsmaschine einen Kurzschluss verursacht und direkt ausgeführt wird.

Wenn Sie dies in Aktion sehen möchten, machen Sie eine asynchrone Methode wie folgt:

%Vor%

Wenn Sie das so nennen:

%Vor%

Sie werden feststellen, dass Sie im Hauptthread bleiben (vorausgesetzt, dass der aktuelle Kontext vor dem Warten vorhanden war), da kein tatsächlicher Async-Code im Codepfad ausgeführt wird. Derselbe Aufruf mit FooAsync(false).ConfigureAwait(false); wird jedoch dazu führen, dass er nach Ausführung zum Threadpool-Thread springt.

    
Reed Copsey 26.03.2014, 21:08
quelle
9

Hier ist die Erklärung dieses Verhaltens basierend auf der .NET-Referenzquelle .

Wenn ConfigureAwait(true) verwendet wird, erfolgt die Fortsetzung über TaskSchedulerAwaitTaskContinuation die SynchronizationContextTaskScheduler verwendet, ist in diesem Fall alles klar.

Wenn ConfigureAwait(false) verwendet wird (oder wenn kein Synchronisationskontext erfasst wird), wird dies über AwaitTaskContinuation , das zuerst versucht, die Fortsetzungsaufgabe inline einzufügen, verwendet dann ThreadPool , um es in die Warteschlange zu stellen, wenn Inlining nicht möglich ist.

Das Inlining wird durch IsValidLocationForInlining bestimmt, was niemals die Inline-Funktion enthält Aufgabe in einem Thread mit einem benutzerdefinierten Synchronisationskontext. Es tut jedoch am besten, es in den aktuellen Pool-Thread einzufügen. Das erklärt, warum wir im ersten Fall auf einen Pool-Thread gedrängt werden und im zweiten Fall auf demselben Pool-Thread bleiben (mit Task.Delay(100) ).

    
Noseratio 26.03.2014 22:52
quelle
8

Ich denke, es ist am einfachsten, sich das etwas anders vorzustellen.

Nehmen wir an, Sie haben:

%Vor%

Erstens, wenn task bereits abgeschlossen ist, dann wird, wie Reed herausstellte, die ConfigureAwait tatsächlich ignoriert und die Ausführung wird fortgesetzt (synchron auf demselben Thread).

Andernfalls wird await die Methode anhalten. In diesem Fall, wenn await fortgesetzt wird und sieht, dass ConfigureAwait ist false , gibt es eine spezielle Logik, um zu überprüfen, ob der Code eine SynchronizationContext hat und in einem Thread-Pool wieder aufzunehmen, falls dies der Fall ist. Dies ist undokumentiertes, aber nicht unangemessenes Verhalten. Da es nicht dokumentiert ist, empfehle ich, dass Sie nicht auf das Verhalten angewiesen sind. Wenn Sie etwas im Thread-Pool ausführen möchten, verwenden Sie Task.Run . ConfigureAwait(false) bedeutet wörtlich "Es ist mir egal, in welchem ​​Kontext diese Methode fortgesetzt wird."

Beachten Sie, dass ConfigureAwait(true) (der Standardwert) die Methode für die aktuelle SynchronizationContext oder TaskScheduler fortsetzt. Während ConfigureAwait(false) die Methode für einen Thread mit Ausnahme von für einen mit SynchronizationContext fortsetzt. Sie sind nicht das genaue Gegenteil von einander.

    
Stephen Cleary 26.03.2014 23:48
quelle