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.

About Reed
Reed Copsey, Jr. - http://www.reedcopsey.com - http://twitter.com/ReedCopsey

Comments

2 Responses to “Synchronizing .NET 4 Tasks with the UI Thread”
  1. tillias says:

    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/

    • Reed says:

      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

Speak Your Mind

Tell us what you're thinking...
and oh, if you want a pic to show with your comment, go get a gravatar!