Synchronizing .NET 4 Tasks with the UI Thread
While attending the Patterns of Parallel Programming Workshop at PDC, I picked up a very interesting tidbit from Stephen Toub’s talk: the new Task pattern in .NET 4 has built-in support for synchronization with the UI thread. This makes writing parallel applications that interact with the user interface easier than ever before.
The key to this is the TaskScheduler.FromCurrentSynchronizationContext() method. This method creates a scheduler from the current SynchronizationContext, automatically choosing the appropriate context. This scheduler can then be used by nearly all of the TaskFactory methods to generate a task.
As an example, here is a very simple Form.Load event handler for a demo Windows Forms application which creates a task which will execute in the background when the form loads. The task runs some (fake) work, and occasionally uses tasks, as well as a continuation task, to update the user interface.
private void Form1_Load(object sender, EventArgs e) { // This requires a label titled "label1" on the form... // Get the UI thread's context var context = TaskScheduler.FromCurrentSynchronizationContext(); this.label1.Text = "Starting task..."; // Start a task - this runs on the background thread... Task task = Task.Factory.StartNew( () => { // Do some fake work... double j = 100; Random rand = new Random(); for (int i = 0; i < 10000000; ++i) { j *= rand.NextDouble(); } // It's possible to start a task directly on // the UI thread, but not common... var token = Task.Factory.CancellationToken; Task.Factory.StartNew(() => { this.label1.Text = "Task past first work section..."; }, token, TaskCreationOptions.None, context); // Do a bit more work Thread.Sleep(1000); }) // More commonly, we'll continue a task with a new task on // the UI thread, since this lets us update when our // "work" completes. .ContinueWith(_ => this.label1.Text = "Task Complete!", context); }
The beauty of this may not be apparent at first glance, but look closely at the threading methods. They work with the SynchronizationContext instance, which was generated by the TaskFactory for us. Nowhere do we explicitly say to use Control.Invoke or any other Windows Forms construct. In fact, this code will work exactly the same way, unchanged, on WPF. The library handles assigning us the correct SynchronizationContext for us. In Windows Forms, it will generate a context which passes everything to the message loop. In WPF, it will automatically generated a context which uses the Dispatcher to push messages back into the WPF message queue.
Hi Reed!
Task doesn’t guarantee that it will be executed in separate thread. I’ve faced several common scenarios where Task’s delegate is executed in GUI thread and thus the application is stuck. Custom TaskScheduler must be used in order to guarantee that task will be executed in separate thread ( and not in the same GUI one).
You can reed about it here: http://tillias.wordpress.com/2010/07/05/net-4-system-threading-tasks-and-wpf-ui-thread/
Tillias,
That’s actually not accurate. The default TaskScheduler, which is what is used when you do Task.Factory.StartNew, will use always use a ThreadPool thread to execute your Task, unless the LongRunning hint is specified, in which case it will use a dedicated thread (in .NET 4’s implementation). Either way, it should NEVER run on the UI thread, since the UI thread should never be on a ThreadPool thread.
Tasks will only get scheduled on the current thread when the current thread is a threadpool thread, or the current thread is blocking on the result of a collection of Tasks (including the one that needs to execute).
That being said, if you want to force a task to run on a dedicated thread, you can do so by specifying the LongRunning hint (in the current implementation). This is a much simpler alternative to creating a custom TaskScheduler. Using an STA thread scheduler is really overkill, unless you specifically need an STA thread.
-Reed
@Reed – I have also found several situations where Task.Factory.StartNew ends up running on the UI Thread.
This shouldn’t happen, normally – though it depends on the current TaskScheduler, as Task.Factory will use the current TaskScheduler to schedule the task.
One potential gotcha here is that if you schedule a continuation for such a task, at least in .NET 4, it will continue to use that TaskScheduler. So there is a risk of invoking follow-up work on the UI thread. I find that most developers aren’t aware of this.
Yes – You typically need to specify TaskScheduler.Default on the continuation if you explicitly want it to not run on the previous thread.
The innertask will be run on UI context. So a Thread.Sleep(1000); will block the UI. TaskScheduler.FromCurrentSynchronizationContext() is only useful for ContinueWith operations
In this case, the inner task is run using the context, which will cause it to run on the UI thread. The Thread.Sleep() call is in the original task, which will not block the UI thread. This works perfectly, without a “ContinueWith” operation.
You can schedule an inner task inside of a task that’s a “UI Task” though, if you make a scheduler for it.
Thanks for the post, helpful!
A much easier way of approaching threading.
Hi Reed.
when starting a Task directly on the UI thread why the need for the cancellation token and TaskCreationOptions?
thanks
Paul
Paul,
This is required only because there is no overload for TaskFactory.StartNew that takes a TaskScheduler but not the token and creation options. The only other option is to create the task, then use Task.Start(scheduler). Otherwise, you need to specify the other items, as the Task API just exposes it that way.
-Reed
Hi,
Thanks for this posting. I needed an easy way to update the user thread in a GUI application when the task finished and this works like a treat. What I did need to do however is change the declaration of the context to
TaskScheduler context = TaskScheduler.FromCurrentSynchronizationContext();
and then pass the context to the task itself as a parameter.
If there is a simple way to update a control on the GUI thread without stopping the GUI thread then I would appreciate an uncomplicated example (like in updating a marqueeControl). I must be missing something because with the ‘old’ background threading scenario you just created an event.
Glen,
One easy approach is actually to take your idea one step futher – instead of making a TaskScheduler and passing it around, you can create a TaskFactory and pass it around:
TaskFactory uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
Then, in your background thread, updating a UI item becomes:
string newText = ComputeText(); // Running in background thread/task
uiFactory.StartNew(() => this.textBox1.Text = newText);
It’s the same idea, but a bit less typing…
-Reed
Thanks again Reed, works like a charm 🙂
Great article. Is there an equivalent to this in native C++ ppl ?
Thanks
Noury
For PPL versions, see: http://blogs.msdn.com/b/nativeconcurrency/archive/2011/03/23/building-responsive-gui-applications-with-ppl-tasks.aspx.
This article covers how to do the same thing using PPL in C++.
Thank you very much for your response.
I looked at the mentioned blog and it appears to address my question but the code seems convoluted to me since i’m new to PPL and I thought that there might be a simpler solution to communicate with the UI as soon as parallel task is done.
For ex. a loop of 10000 that is running in parallel some operation (in ex below: _sleep(5000).
Would there be away to tell the ui progress bar i’m now done with the executed operation x (by incrementing the progress bar without blocking)
int loopCount = 10000;
ProgressBar *myProgressBar(0, loopCount); // Creating the progress bar GUI (MFC)
myProgressBar->Show();
task_group *tg = new task_group();
tg->run_and_wait([&]{
parallel_for(0, loopCount, [&] (int i)
{
_sleep(5000); // Do some long work here
// myProgressBar.StepIt(); // This block the parallel loop !!
});
});
Thanks again
Noury
Noury,
Sorry, but I don’t know the PPL well enough to guide you here. I’ve only used it in pure-compute scenarios where I wasn’t trying to marshal calls back to a main thread. I typically use C# for all of my UI related work.
I’d recommend asking in the PPL forums or on one of the concurrency runtime blogs.
-Reed
A very simple and beautiful way to sync Tasks with UI thread. Great article. Thanks.
Wonderful article.
Thanks for your sample code. it’s quite useful.
Hi Reed,
first of all it is funny that your name is “Reed” because my nickname given by my friends is also “Reed”.
But back to topic: Your example helped me a lot. Thanks for it! I use it to load big amounts of data from database.
Best regards from germany,
Jürgen
Hi Reed,
Finally a great and pointed article that demonstrates TAP/TPL approach to Winforms multithreading. It helped me a lot to re-understand the usage and the logic behind.
In past, we would struggle with delegates/control’s invoke/begininvoke methods too much and this simplifies lots of things for developers who are not still familiar with async/await pattern which is newer and simplier.
I would like to see more and real examples with various scenarios on this pattern using TASK class, TAP.
Thanks again and have a nice time!
I have been poring through web examples for days looking to cobble together a solution.
I am new to async / threads. This article (along with a lot of study on threads) helped me get my project to do what it needed. Apparently it’s just as applicable in .Net 4.7 for WPF as it was in 2009! Simple and easy to grasp.
Thanks!
Thanks. Very helpful. I also liked the comment you made. It should be in the original post.
TaskFactory uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
Then, in your background thread, updating a UI item becomes:
string newText = ComputeText(); // Running in background thread/task
uiFactory.StartNew(() => this.textBox1.Text = newText);