Ich dachte, ich hätte das asynchrone Wartemuster und die Task.Run
Operation verstanden.
Ich frage mich jedoch, warum im folgenden Codebeispiel die await
Synchronisierung nach der Rückkehr von der abgeschlossenen Aufgabe nicht wieder mit dem UI-Thread synchronisiert wird.
public async Task InitializeAsync()
{
Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}"); // "Thread: 1"
double value = await Task.Run(() =>
{
Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}"); // Thread: 6
// Do some CPU expensive stuff
double x = 42;
for (int i = 0; i < 100000000; i++)
{
x += i - Math.PI;
}
return x;
}).ConfigureAwait(true);
Console.WriteLine($"Result: {value}");
Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}"); // Thread: 6 - WHY??
}
Dieser Code wird in einer .NET Framework WPF-Anwendung auf einem Windows 10-System mit angeschlossenem Visual Studio 2019-Debugger ausgeführt.
Ich rufe diesen Code vom Konstruktor meiner App
Klasse auf.
public App()
{
this.InitializeAsync().ConfigureAwait(true);
}
Vielleicht ist es nicht der beste Weg, aber ich bin mir nicht sicher, ob dies der Grund für das seltsame Verhalten ist.
Der Code beginnt mit dem UI-Thread und sollte einige Aufgaben ausführen. Mit der await
Operation und ConfigureAwait(true)
nach Beendigung der Aufgabe sollte sie auf dem Haupt-Thread (1) fortgesetzt werden. Aber das tut es nicht.
Warum?
quelle
Antworten:
Es ist eine schwierige Sache.
Sie rufen den
await
UI-Thread auf, das stimmt. Aber! Sie machen es imApp
Konstruktor.Denken Sie daran, dass der implizit generierte Startcode folgendermaßen aussieht:
Die Ereignisschleife, die zum Zurückkehren zum Hauptthread verwendet wird, wird nur als Teil der
Run
Ausführung gestartet . Während desApp
Konstruktorlaufs gibt es also keine Ereignisschleife. Noch.Infolgedessen befindet sich der
SynchronizationContext
, der technisch für die Rückgabe des Flusses an den Haupt-Thread nach verantwortlichawait
ist,null
beim Konstruktor der App.(
SynchronizationContext
Wird vonawait
vor dem Warten erfasst , es spielt also keine Rolle, dass nach Abschluss desTask
Vorgangs bereits ein gültigerSynchronizationContext
Wert vorhanden ist: Der erfasste Wert istnull
, setzt alsoawait
die Ausführung in einem Thread-Pool-Thread fort.)Das Problem ist also nicht, dass Sie den Code in einem Konstruktor ausführen, sondern dass Sie ihn im Konstruktor des
App
Konstruktors ausführen. Zu diesem Zeitpunkt ist die Anwendung noch nicht vollständig für die Ausführung eingerichtet. Der gleiche Code imMainWindow
Konstruktor würde sich gut verhalten.Lassen Sie uns ein Experiment machen:
Die erste Ausgabe gibt
der Zweite
Sie sehen also, dass bereits
OnStartup
ein Synchronisationskontext vorhanden ist. Also , wenn Sie sich bewegenInitializeAsync()
inOnStartup
, wird es sich verhalten , wie Sie es erwarten würden.quelle