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

22 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

      • Andrew says:

        @Reed – I have also found several situations where Task.Factory.StartNew ends up running on the UI Thread.

        • Reed says:

          This shouldn’t happen, normally – though it depends on the current TaskScheduler, as Task.Factory will use the current TaskScheduler to schedule the task.

          • Philipp says:

            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.

          • Reed says:

            Yes – You typically need to specify TaskScheduler.Default on the continuation if you explicitly want it to not run on the previous thread.

    • Simon Müller says:

      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

      • Reed says:

        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.

  2. Oliver says:

    Thanks for the post, helpful!

    A much easier way of approaching threading.

  3. Paul says:

    Hi Reed.

    when starting a Task directly on the UI thread why the need for the cancellation token and TaskCreationOptions?

    thanks
    Paul

    • Reed says:

      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

  4. Glen Harvy says:

    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.

    • Reed says:

      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

  5. Noury says:

    Great article. Is there an equivalent to this in native C++ ppl ?

    Thanks

    Noury

      • Noury says:

        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

        • Reed says:

          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

  6. Harry says:

    A very simple and beautiful way to sync Tasks with UI thread. Great article. Thanks.

  7. kav says:

    Wonderful article.
    Thanks for your sample code. it’s quite useful.

Trackbacks

Check out what others are saying about this post...
  1. […] This post shows some code that very simply and clearly demonstrates how to synchronize background tasks with the main thread that handles the GUI. http://reedcopsey.com/2009/11/17/synchronizing-net-4-tasks-with-the-ui-thread/ […]

  2. […] do this so that we could write the results of the Task into the user interface.  There are several articles on the internet that describe […]



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!