EaranMaleasi schrieb:
So mal ganz nebenbei, normalerweise, sofern man mit Async/Await richtig umgeht, benötigt man keine Invoke()/BeginInvoke() aufrufe mehr.
Man muss beachten, dass der Sync.-Context bei einem Task.Run() nicht gesetzt wird. Ein Beispiel:
Wir haben eine Methode die lange braucht und dann und wann mal ein UI Update macht:
Sie gibt einen Task zurück und ist awaitable (durch das async Keyword). Man könnte nun auf die Idee kommen und diese Methode mit Task.Run() zu starten:
private Button_Click(object sender, EventArgs e) => Task.Run(LongRunningMethod);
Nun wird man allerdings mit einer InvalidOperationException begrüßt. Macht auch Sinn. Es passiert Folgendes: Der EventHandler des Buttons läuft ganz regulär synchron im UI-Thread. Task.Run() startet einen neuen Thread, in welchem auf die UI zugegriffen wird => Rumms! Das kann nie klappen. Macht man es allerdings so:
private async void button_Click(object sender, EventArgs e) => await LongRunningMethod();
funktioniert es wie geschmiert. Aber warum? Durch die async Deklaration des EventHandlers laufen await Aufrufe asynchron zur UI. Ein Task.Run() ist hier also total überflüssig. Jetzt aber zur Preisfrage
Die
LongRunningMethod
läuft ja in beiden Fällen asynchron und greift auf die UI zu. Warum knallt es beim Ersten aber nicht beim Zweiten?Grundsätzlich müsste es auch im zweiten Fall knallen, aber das Control setzt mit
WindowsFormsSynchronizationContext.InstallIfNeeded()
den SynchronizationContext. Das gibt dem Thread die Möglichkeit den Code synchron zum UI Thread auszuführen. Ist eine Synchronisation dann Notwendig, passierts automatisch und Invoken kann man sich schenken.Es funktioniert also in etwa so:
C#-Quellcode
- private void button_Click(object sender, EventArgs e)
- {
- Thread thread = new Thread(LongRunningMethod);
- thread.Start(SynchronizationContext.Current);
- }
- private int nopValue = 0;
- private void LongRunningMethod(object context)
- {
- while (true)
- {
- nopValue++;
- Thread.Sleep(1000);
- var syncContext = context as SynchronizationContext;
- if (syncContext != null)
- syncContext.Post(new SendOrPostCallback((o) => Text = nopValue.ToString()), null);
- }
- }
Naja eigentlich nicht, aber so versteht man mal wie man mit
Tasks
überhaupt asynchron auf die UI zugreifen kann. Das ganze Zeugs mit dem SynchronizationContext läuft natürlich fein im Hintergrund. Deswegen ist async/await ja so bequem. Man muss nix synchronisieren oder Invoken, das wird alles für einen gemacht. Zusammenfassend:
Wenn du also deinen EventHandler als
async
deklarierst wird dir direkt der passende Sync.Context gesetzt. Alle UI zugriffe können so synchronisiert werden. Deklariere eine Methode als async
wenn du auf Code mit await
warten willst.