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%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:
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.
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)
).
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.
Tags und Links .net c# multithreading task-parallel-library async-await