2010-12-02 4 views
174

Gibt es eine "Standard" -Methode, um anzugeben, dass eine Taskfortsetzung in dem Thread ausgeführt werden soll, aus dem die ursprüngliche Task erstellt wurde?Taskfortsetzung auf dem UI-Thread

Derzeit habe ich den Code unten - es funktioniert, aber den Dispatcher verfolgen und eine zweite Aktion erstellen scheint wie unnötiger Aufwand.

dispatcher = Dispatcher.CurrentDispatcher; 
Task task = Task.Factory.StartNew(() => 
{ 
    DoLongRunningWork(); 
}); 

Task UITask= task.ContinueWith(() => 
{ 
    dispatcher.Invoke(new Action(() => 
    { 
     this.TextBlock1.Text = "Complete"; 
    } 
}); 
+0

In Ihrem Beispiel könnten Sie 'Control.Invoke (Action)' verwenden, dh. 'TextBlock1.Invoke' statt' dispatcher.Invoize' –

+0

Dank @ColonelPanic, aber ich war mit WPF (wie markiert), nicht winforms. –

Antwort

286

Anruf der Fortsetzung mit TaskScheduler.FromCurrentSynchronizationContext():

Task UITask= task.ContinueWith(() => 
    { 
    this.TextBlock1.Text = "Complete"; 
    }, TaskScheduler.FromCurrentSynchronizationContext()); 

Dies ist nur geeignet, wenn der aktuelle Ausführungskontext auf dem UI-Thread ist.

+33

Es ist nur gültig, wenn sich der aktuelle Ausführungskontext im UI-Thread befindet. Wenn Sie diesen Code in eine andere Aufgabe einfügen, erhalten Sie InvalidOperationException (siehe [Ausnahmen] (http://msdn.microsoft.com/en-us/library/system.threading.tasks.taskscheduler.fromcurrentsynchronizationcontext (v = vs. 110) .aspx) Abschnitt) – stukselbax

+0

In .NET 4.5 sollte die Antwort von Johan Larsson als Standardmethode für eine Taskfortsetzung auf dem UI-Thread verwendet werden. Schreiben Sie einfach: warten Task.Run (DoLongRunningWork); this.TextBlock1.Text = "Abgeschlossen"; Siehe auch: http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx –

18

Wenn Sie einen Rückgabewert müssen Sie die Benutzeroberfläche senden Sie die generische Version wie folgt verwenden können:

Dies ist aus einem MVVM Ansichtsmodell in meinem Fall aufgerufen wird.

var updateManifest = Task<ShippingManifest>.Run(() => 
    { 
     Thread.Sleep(5000); // prove it's really working! 

     // GenerateManifest calls service and returns 'ShippingManifest' object 
     return GenerateManifest(); 
    }) 

    .ContinueWith(manifest => 
    { 
     // MVVM property 
     this.ShippingManifest = manifest.Result; 

     // or if you are not using MVVM... 
     // txtShippingManifest.Text = manifest.Result.ToString();  

     System.Diagnostics.Debug.WriteLine("UI manifest updated - " + DateTime.Now); 

    }, TaskScheduler.FromCurrentSynchronizationContext()); 
+0

Ich vermute das = bevor GenerateManifest ist ein Tippfehler. –

+0

Ja - jetzt weg! Danke. –

9

Ich wollte nur diese Version hinzufügen, weil dies ein so nützlicher Thread ist und ich denke, das ist eine sehr einfache Implementierung.

Task.Factory.StartNew(() => 
     { 
     DoLongRunningWork(); 
     Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => 
       { txt.Text = "Complete"; })); 
     }); 
+2

Nicht absteigend, da dies in einigen Szenarien eine praktikable Lösung ist; Die angenommene Antwort ist jedoch viel besser. Es ist technologieunabhängig ('TaskScheduler' ist Teil von BCL,' Dispatcher' ist es nicht) und kann verwendet werden, um komplexe Aufgabenketten zu erstellen, da man sich nicht um asynchrone Fire-and-Forget-Operationen kümmern muss (z. B. BeginInvoke) '). –

+0

@Kirill können Sie etwas erweitern, da einige SO-Threads den Dispatcher einstimmig als die richtige Methode deklariert haben, wenn Sie WPF von WinForms verwenden: Ein GUI-Update kann entweder asynchron (mit BeginInvoke) oder synchron (Invoke) aufgerufen werden async wird verwendet, weil man einen Hintergrund-Thread nicht nur für ein GUI-Update blockieren möchte. Setzt FromCurrentSynchronizationContext den Fortsetzungs-Task nicht genauso in die Haupt-Thread-Nachrichtenwarteschlange wie der Dispatcher? – Dean

+0

sichere Sache. Ich werde zuerst das Problem "Dispatcher" behandeln. 'Dispatcher.Invoke/BeginInvoke' ist ein WPF-Konzept; Sein Gegenstück in WinForms wäre "Control.Invoke/BeginInvoke". Jetzt müssen Sie Ihren Code an die spezifische Plattform anpassen, mit der Sie arbeiten - was nicht der Fall wäre, wenn Sie mit "TaskScheduler.FromCurrentSynchronizationContext" an erster Stelle stehen würden, da es Teil der Basisklassenbibliothek ist daher weit verbreitet. –

21

Mit async Sie gerade tun:

await Task.Run(() => do some stuff); 
// continue doing stuff on the same context as before. 
// while it is the default it is nice to be explicit about it with: 
await Task.Run(() => do some stuff).ConfigureAwait(true); 

jedoch:

await Task.Run(() => do some stuff).ConfigureAwait(false); 
// continue doing stuff on the same thread as the task finished on. 
0

J ust schreiben Sie Ihren Code wie ich dies mehrere Male in verschiedenen Typen, wenn Multithread-Anwendung verwendet (Aber mit ContinueWith ist eine gute Praxis, mach dir keine Sorgen über unnötigen Overhead für die Laufzeit)

Task task = Task.Factory.StartNew(() => 
{ 
    DoLongRunningWork(); 
    Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => 
    { 
     this.TextBlock1.Text = "Complete"; 
    } 
}); 

Setzen Dispatcher Code in finally Block, wenn Sie sicherstellen wollen dies auszuführen.

VersuchenTaskScheduler.FromCurrentSynchronizationContext() wie aus der Nutzung dieser UI-Vermeidung Thread kann von Ihrem aktuellen Thread blockiert werden.

+0

Dies ist praktisch das gleiche wie @Deans antworten. –

+0

@GregSansom ja aus Code-Perspektive, aber ich habe auch einige Hinweise hinzugefügt, die auch sehr wichtig sind, in diesem Zusammenhang zu wissen und sind auch auf andere Antwort bezogen. mögen. Verwendung von TaskScheduler als die Antwort, die rechts markiert ist, aber einige Probleme verursachen kann. :) –

+1

Dann sollten Sie Ihre Notizen als Kommentar nicht als vollständige Antwort hinzugefügt haben. – pix