I have a main thread that contains my WPF GUI and one or more background threads that occassionally need to execute code in the main thread asynchronously (e.g. a status update in the GUI).
There are two ways (that I know of, maybe more) to accomplish this:
- by scheduling a task using the
TaskSchedulerfrom the target thread’s synchronization context, and - by invoking a delegate using the
Dispatcherfrom the target thread.
In code:
using System.Threading.Tasks;
using System.Threading;
Action mainAction = () => MessageBox.Show(string.Format("Hello from thread {0}", Thread.CurrentThread.ManagedThreadId));
Action backgroundAction;
// Execute in main thread directly (for verifiying the thread ID)
mainAction();
// Execute in main thread via TaskScheduler
var taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
backgroundAction = () => Task.Factory.StartNew(mainAction, CancellationToken.None, TaskCreationOptions.None, taskScheduler);
Task.Factory.StartNew(backgroundAction);
// Execute in main thread via Dispatcher
var dispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher;
backgroundAction = () => dispatcher.BeginInvoke(mainAction);
Task.Factory.StartNew(backgroundAction);
I like the TPL-based version, because I already use TPL a lot and it provides me with a lot of flexibility, e.g. by waiting on the task or chaining it with other tasks. However, in most examples of WPF code I have seen so far, the Dispatcher variant was used.
Assuming I didn’t need any of that flexibility and only wanted to execute some code in the target thread, what are the reasons one would prefer one way over the other? Are there any performance implications?
I highly recommend that you read the Task-based Asynchronous Pattern document. This will allow you to structure your APIs to be ready when
asyncandawaithit the streets.I used to use
TaskSchedulerto queue updates, similar to your solution (blog post), but I no longer recommend that approach.The TAP document has a simple solution that solves the problem more elegantly: if a background operation wants to issue progress reports, then it takes an argument of type
IProgress<T>:It’s then relatively simple to provide a basic implementation:
(
SynchronizationContext.Currentis essentiallyTaskScheduler.FromCurrentSynchronizationContextwithout the need for actualTasks).The Async CTP contains
IProgress<T>and aProgress<T>type that is similar to theEventProgress<T>above (but more performant). If you don’t want to install CTP-level stuff, then you can just use the types above.To summarize, there are really four options:
IProgress<T>– this is the way asynchronous code in the future will be written. It also forces you to separate your background operation logic from your UI/ViewModel update code, which is a Good Thing.TaskScheduler– not a bad approach; it’s what I used for a long time before switching toIProgress<T>. It doesn’t force the UI/ViewModel update code out of the background operation logic, though.SynchronizationContext– same advantages and disadvantages toTaskScheduler, via a lesser-known API.Dispatcher– really can not recommend this! Consider background operations updating a ViewModel – so there’s nothing UI-specific in the progress update code. In this case, usingDispatcherjust tied your ViewModel to your UI platform. Nasty.P.S. If you do choose to use the Async CTP, then I have a few additional
IProgress<T>implementations in my Nito.AsyncEx library, including one (PropertyProgress) that sends the progress reports throughINotifyPropertyChanged(after switching back to the UI thread viaSynchronizationContext).