2013-04-25 11 views
7

In Bezug auf dieses Stück Code in WPF UI-Thread ausgeführt:Warum Async Rückruf

static async Task<string> testc() 
{ 
    Console.WriteLine("helo async " + Thread.CurrentThread.ManagedThreadId); 
    await Task.Run(() => { 
     Thread.Sleep(1000); 
     Console.WriteLine("task " + Thread.CurrentThread.ManagedThreadId); 
    }); 
    Console.WriteLine("callback "+Thread.CurrentThread.ManagedThreadId); 
    return "bob"; 
} 

static void Main(string[] args) 
{ 
    Console.WriteLine("helo sync " + Thread.CurrentThread.ManagedThreadId); 
    testc(); 
    Console.WriteLine("over" + Thread.CurrentThread.ManagedThreadId); 
    Thread.Sleep(2000); 
    Console.ReadLine(); 
} 

ich folgende Ausgabe:

helo sync 10 
helo async 10 
over10 
task 11 
callback **11** 

Welche OK ist: Stück Code nach await ausgeführt wird, in der gleiche Thread wie die Aufgabe selbst.

Nun, wenn ich es in einer WPF-Anwendung zu tun:

private void Button_Click_1(object sender, RoutedEventArgs e) 
{ 
    Console.WriteLine("helo sync " + Thread.CurrentThread.ManagedThreadId); 
    testc(); 
    Console.WriteLine("over" + Thread.CurrentThread.ManagedThreadId); 
    Thread.Sleep(2000); 
    Console.ReadLine(); 
} 

Es erzeugt folgende Ausgabe:

helo sync 8 
helo async 8 
over8 
task 9 
callback **8** 

Wo wir Code sehen können, nachdem sie in dem UI-Thread ausgeführt abzuwarten. Nun, das ist großartig, weil es das Manipulieren beobachtbarer Sammlungen usw. ermöglicht. Aber ich habe mich gefragt, warum? "Wie könnte ich das Gleiche machen?" Handelt es sich um ein TaskScheduler-Verhalten? Ist das im .NET Framework fest programmiert?

Thx für jede Idee, die Sie einreichen können.

Antwort

7

Der Grund dafür ist, dass Task.Run die SynchronizationContext erfasst, wenn eine solche in einer WPF-Anwendung vorhanden ist, wenn die Task vom UI-Thread gestartet wird. Die Task verwendet dann die SynchronizationContext, um den Rückruf zum UI-Thread zu serialisieren. Wenn jedoch kein Kontext wie in einer Konsolenanwendung verfügbar ist, wird der Rückruf in einem anderen Thread ausgeführt.

Stephen Toub hat dies in einer blog entry beschrieben.

BTW, Seien Sie vorsichtig, wenn Sie verwenden nie Thread.Sleep in einer Aufgabe verwenden.Es kann seltsames Verhalten verursachen, da eine Aufgabe möglicherweise nicht an einen Thread gebunden ist. Verwenden Sie stattdessen Task.Delay.

+0

+1 braucht mehr Betonung auf Ihre BTW obwohl. – Phill

2

Aber ich fragte mich "Warum?"

Sie haben geantwortet, dass selbst:

Nun, das ist großartig, da es beobachtbare Sammlungen Manipulation usw. ermöglicht ...

Der ganze Sinn der async ist Asynchronität zu erleichtern um damit zu arbeiten - so können Sie "synchron aussehenden" Code schreiben, der eigentlich asynchron ist. Das beinhaltet häufig, dass Sie in einem Kontext (z. B. einem UI-Thread) für die gesamte asynchrone Methode bleiben möchten - indem Sie die Methode einfach "anhalten" (ohne den UI-Thread zu blockieren), wenn Sie auf etwas warten müssen.

"Wie könnte ich das Gleiche machen?"

Es ist nicht klar, was Sie hier meinen. Im Grunde genommen verwendet die Implementierung des erwarteten Musters für TaskTaskScheduler.FromCurrentSynchronizationContext(), um heraus zu finden, welcher Scheduler den Callback anmeldet - es sei denn, Sie haben ConfigureAwait(false) aufgerufen, um dieses Verhalten ausdrücklich zu deaktivieren. So wird es gehandhabt ... Ob Sie das "Gleiche" tun können oder nicht, hängt davon ab, was genau Sie tun wollen.

Weitere Details zu dem erwarteten Muster finden Sie in der Frage "Was sind die Erwartungen?" In der async/await FAQ.

+0

Von "Wie könnte ich das gleiche tun?" Ich denke, er meint: "Wie bekomme ich das WPF-Verhalten, ohne WPF zu benutzen?" –

+0

@ jdv-JandeVaan: Wenn das die Bedeutung war, ist es extrem unklar. Wie auch immer, hoffentlich werden die Details, die ich gegeben habe, genug Informationen geben, um das OP weiter zu machen. Sie können jederzeit nach weiteren Details fragen. –

+0

Ok .. Fein. Ich möchte das nicht wirklich machen. Ich habe mich gefragt, wie der Rückruf an Dispatcher.Invoke-Methode gesendet wird ... und wenn ich könnte schließlich eine ähnliche Sache oder wenn es im Framework fest codiert wurde. Thx für Ihre Antwort – Kek

0

Sie können meine async/await intro hilfreich finden. Die anderen Antworten sind fast korrekt.

Wenn Sie await ein Task, die noch nicht abgeschlossen ist, wird standardmäßig ein „Kontext“ erfasst, die das Verfahren zur Wiederaufnahme verwendet wird, wenn die Task abgeschlossen ist. Dieser "Kontext" ist SynchronizationContext.Current, es sei denn, es ist null, in diesem Fall ist es TaskScheduler.Current.

Beachten Sie die dafür erforderlichen Bedingungen zu arbeiten:

  • „Wenn man await ...“ - wenn Sie eine Fortsetzung planen manuell, beispielsweise mit Task.ContinueWith, wird keine Kontexterfassung erfolgt. Sie müssen es selbst tun mit etwas wie (SynchronizationContext.Current == null ? TaskSchedler.Current : TaskScheduler.FromCurrentSynchronizationContext()).
  • "... await ein Task ..." - dieses Verhalten Teil des await Verhalten ist für Task Typen. Andere Typen können eine ähnliche Erfassung durchführen oder nicht.
  • „..., die noch nicht abgeschlossen ... hat“ - wenn die Task durch die Zeit bereits abgeschlossen ist, es ist await ed, die async Methode synchron fortsetzen wird. Es ist also nicht notwendig, in diesem Fall den Kontext zu erfassen.
  • "... standardmäßig ..." - dies ist das Standardverhalten und kann geändert werden. Rufen Sie insbesondere die Methode Task.ConfigureAwait auf und übergeben Sie false für den Parameter continueOnCapturedContext. Diese Methode gibt einen erwarteten Typ (nicht Task) zurück, der im erfassten Kontext nicht fortgesetzt wird, wenn der Parameter false lautet.
+0

Thx für diese vollständige Antwort. Das meiste davon (oder zumindest genug, um meine Frage zu beantworten) war der Link in @ Jacob Christensens Antwort, aber ich schätze diesen vollen Standpunkt! – Kek