45

I am using Dispatcher to switch to UI thread from external like this

Application.Current.Dispatcher.Invoke(myAction);

But I saw on some forums people have advised to use SynchronizationContext instead of Dispatcher.

SynchronizationContext.Current.Post(myAction,null);

What is the difference between them and why SynchronizationContext should be used?.

Eternal21
  • 4,190
  • 2
  • 48
  • 63
TRS
  • 1,947
  • 4
  • 26
  • 49

5 Answers5

39

They both have similar effects, but SynchronizationContext is more generic.

Application.Current.Dispatcher refers to the WPF dispatcher of the application, and using Invoke on that executes the delegate on the main thread of that application.

SynchronizationContext.Current on the other hand returns different implementations of depending on the current thread. When called on the UI thread of a WPF application it returns a SynchronizationContext that uses the dispatcher, when called in on the UI thread of a WinForms application it returns a different one.

You can see the classes inheriting from SynchronizationContext in its MSDN documentation: WindowsFormsSynchronizationContext and DispatcherSynchronizationContext.


One thing to be aware about when using SynchronizationContext is that it returns the synchronization context of the current thread. If you want to use the synchronization context of another thread, e.g. the UI thread, you have to first get its context and store it in a variable:

public void Control_Event(object sender, EventArgs e)
{
    var uiContext = SynchronizationContext.Current;
    Task.Run(() => 
    {
        // do some work
        uiContext.Post(/* update UI controls*/);
    }
}

This does not apply to Application.Current.Dispatcher, which always returns the dispatcher for the application.

Dirk
  • 10,668
  • 2
  • 35
  • 49
  • 2
    Small note - Task.ContinueWith lets you schedule a continuation on the ui without having to store the context manually http://stackoverflow.com/questions/4331262/task-continuation-on-ui-thread – Gusdor Jul 10 '14 at 08:52
  • 1
    @Gusdor Sure, but the point is to show an example of `SynchronizationContext`. – Dirk Jul 10 '14 at 08:53
  • @Gusdor, in the example you linked, a new task has to be created first. How do you use `Task.ContinueWith()` without a task to continue? – Kyle Delaney Jul 06 '17 at 14:43
  • @KyleDelaney An existing task is a requirement of `ContinueWith`. Are you trying to solve a specific problem? Maybe a new question would be best. – Gusdor Jul 06 '17 at 14:48
  • Well I'm pointing out why your suggestion doesn't work as a replacement for this usage of `SynchronizationContext`. – Kyle Delaney Jul 06 '17 at 15:44
  • There's also the ASP.NET synchronization context (see https://stackoverflow.com/questions/12659851/aspnetsynchronizationcontext) – riQQ Aug 08 '19 at 13:45
25

When using WPF, the SynchronizationContext.Current object is of type DispatcherSynchronizationContext which is actually just a wrapper around the Dispatcher object and the Post and Send methods just delegate to Dispatcher.BeginInvoke and Dispatcher.Invoke.

So even if you decide to use SynchronizationContext I think you end up calling dispatcher behind the scenes.

Besides I think it is a bit cumbersome to use SynchronizationContext as you have to pass a reference to the current context to all threads that need to call into your UI.

MRebai
  • 5,344
  • 3
  • 33
  • 52
  • 5
    @NikhilAgrawal http://referencesource.microsoft.com/#WindowsBase/Base/System/Windows/Threading/DispatcherSynchronizationContext.cs – Eldar Oct 08 '15 at 20:16
  • 2
    As explain in other answers, using `SynchronizationContext` instead of `Dispatcher` enables to abstract the UI from the logic, helps mocking the UI when unit testing and better fits with MVVM – Eliahu Aaron Dec 10 '19 at 15:29
22

While the differences have been pointed out, I don't really see that reason for choosing one over another explicitly spelled out here. So perhaps it would help to explain what problem the SynchronizationContext object is trying to solve in the first place:

  1. It provides a way to queue a unit of work to a context. Notice that this isn't thread specific, and so we avoid the problem of thread affinity.
  2. Every thread has a "current" context, but that context might be shared across threads i.e. a context is not necessarily unique.
  3. Contexts keep a count of outstanding asynchronous operations. This count is often, but not always incremented/decremented on capture/queuing.

So to answer your question of which one to choose, it would seem just from the criteria above that using the SynchronizationContext would be preferable to the Dispatcher.

But there are even more compelling reasons to do so:

  • Separation of concerns

By using the SynchronizationContext to handle executing code on the UI thread, you can now easily separate your operations from the display via decoupled interface(s). Which leads to the next point:

  • Easier unit testing

If you have ever tried to mock an object as complex as the Dispatcher versus the SynchronizationContext, which has far fewer methods to deal with, you will quickly come to appreciate the far simpler interface offered by the SynchronizationContext.

  • IoC and Dependency Injection

As you have already seen, the SynchronizationContext is implemented across many UI frameworks: WinForms, WPF, ASP.NET, etc. If you write your code to interface to one set of APIs, your code becomes more portable and simpler to maintain as well as test.

You don't need to even inject a context object... you can inject any object with an interface that matches the methods on the context object, including proxies.

By way of example:

Note: I have left out exception handling to make the code clear.

Suppose we have a WPF application that has a single button. Upon clicking that button, you will start a long process of asynchronous work tasks interlaced with UI updates, and you need to coordinate IPC between the two.

Using WPF and the traditional Dispatch approach, you might code something like this:

/// <summary>
/// Start a long series of asynchronous tasks using the `Dispatcher` for coordinating
/// UI updates.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Start_Via_Dispatcher_OnClick(object sender, RoutedEventArgs e)
{
  // update initial start time and task status
  Time_Dispatcher.Text = DateTime.Now.ToString("hh:mm:ss");
  Status_Dispatcher.Text = "Started";

  // create UI dont event object
  var uiUpdateDone = new ManualResetEvent(false);

  // Start a new task (this uses the default TaskScheduler, 
  // so it will run on a ThreadPool thread).
  Task.Factory.StartNew(async () =>
  {
    // We are running on a ThreadPool thread here.

    // Do some work.
    await Task.Delay(2000);

    // Report progress to the UI.
    Application.Current.Dispatcher.Invoke(() =>
    {
      Time_Dispatcher.Text = DateTime.Now.ToString("hh:mm:ss");

      // signal that update is complete
      uiUpdateDone.Set();
    });

    // wait for UI thread to complete and reset event object
    uiUpdateDone.WaitOne();
    uiUpdateDone.Reset();

    // Do some work.
    await Task.Delay(2000); // Do some work.

    // Report progress to the UI.
    Application.Current.Dispatcher.Invoke(() =>
    {
      Time_Dispatcher.Text = DateTime.Now.ToString("hh:mm:ss");

      // signal that update is complete
      uiUpdateDone.Set();
    });

    // wait for UI thread to complete and reset event object
    uiUpdateDone.WaitOne();
    uiUpdateDone.Reset();

    // Do some work.
    await Task.Delay(2000); // Do some work.

    // Report progress to the UI.
    Application.Current.Dispatcher.Invoke(() =>
    {
      Time_Dispatcher.Text = DateTime.Now.ToString("hh:mm:ss");

      // signal that update is complete
      uiUpdateDone.Set();
    });

    // wait for UI thread to complete and reset event object
    uiUpdateDone.WaitOne();
    uiUpdateDone.Reset();
  },
  CancellationToken.None,
  TaskCreationOptions.None,
  TaskScheduler.Default)
    .ConfigureAwait(false)
    .GetAwaiter()
    .GetResult()
    .ContinueWith(_ =>
    {
      Application.Current.Dispatcher.Invoke(() =>
      {
        Status_Dispatcher.Text = "Finished";

        // dispose of event object
        uiUpdateDone.Dispose();
      });
    });
}

This code works as intended, but has the following drawbacks:

  1. The code is tied to the WPF Application Dispatcher object. This makes this difficult to unit test and abstract.
  2. The need for an external ManualResetEvent object for synchronizing between threads. This should immediately set off a code smell, since this now depends on another resource that needs to be mocked.
  3. Difficulty in managing object lifetime for said same kernel object.

Now, lets try this again using the SynchronizationContext object:

/// <summary>
/// 
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Start_Via_SynchronizationContext_OnClick(object sender, RoutedEventArgs e)
{
  // update initial time and task status
  Time_SynchronizationContext.Text = DateTime.Now.ToString("hh:mm:ss");
  Status_SynchronizationContext.Text = "Started";

  // capture synchronization context
  var sc = SynchronizationContext.Current;

  // Start a new task (this uses the default TaskScheduler, 
  // so it will run on a ThreadPool thread).
  Task.Factory.StartNew(async () =>
  {
    // We are running on a ThreadPool thread here.

    // Do some work.
    await Task.Delay(2000);

    // Report progress to the UI.
    sc.Send(state =>
    {
      Time_SynchronizationContext.Text = DateTime.Now.ToString("hh:mm:ss");
    }, null);

    // Do some work.
    await Task.Delay(2000);

    // Report progress to the UI.
    sc.Send(state =>
    {
      Time_SynchronizationContext.Text = DateTime.Now.ToString("hh:mm:ss");
    }, null);

    // Do some work.
    await Task.Delay(2000);

    // Report progress to the UI.
    sc.Send(state =>
    {
      Time_SynchronizationContext.Text = DateTime.Now.ToString("hh:mm:ss");
    }, null);
  },
  CancellationToken.None,
  TaskCreationOptions.None,
  TaskScheduler.Default)
  .ConfigureAwait(false)
  .GetAwaiter()
  .GetResult()
  .ContinueWith(_ =>
  {
    sc.Post(state =>
    {
      Status_SynchronizationContext.Text = "Finished";
    }, null);
  });
}

Notice this time through, we dont need to rely on external objects for synchronizing between threads. We are, in fact, synchronizing between contexts.

Now, even though you didn't ask, but for the sake of completeness, there is one more way to accomplish what you want in an abstracted way without the need for the SynchronizationContext object or using the Dispatcher. Since we are already using the TPL (Task Parallel Library) for our task handling, we could just use the task scheduler as follows:

/// <summary>
/// 
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Start_Via_TaskScheduler_OnClick(object sender, RoutedEventArgs e)
{
  Time_TaskScheduler.Text = DateTime.Now.ToString("hh:mm:ss");


  // This TaskScheduler captures SynchronizationContext.Current.
  var taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
  Status_TaskScheduler.Text = "Started";

  // Start a new task (this uses the default TaskScheduler, 
  // so it will run on a ThreadPool thread).
  Task.Factory.StartNew(async () =>
  {
    // We are running on a ThreadPool thread here.

    // Do some work.
    await Task.Delay(2000);

    // Report progress to the UI.
    var reportProgressTask = ReportProgressTask(taskScheduler, () =>
    {
      Time_TaskScheduler.Text = DateTime.Now.ToString("hh:mm:ss");
      return 90;
    });

    // get result from UI thread
    var result = reportProgressTask.Result;
    Debug.WriteLine(result);

    // Do some work.
    await Task.Delay(2000); // Do some work.

    // Report progress to the UI.
    reportProgressTask = ReportProgressTask(taskScheduler, () =>
      {
        Time_TaskScheduler.Text = DateTime.Now.ToString("hh:mm:ss");
        return 10;
      });

    // get result from UI thread
    result = reportProgressTask.Result;
    Debug.WriteLine(result);

    // Do some work.
    await Task.Delay(2000); // Do some work.

    // Report progress to the UI.
    reportProgressTask = ReportProgressTask(taskScheduler, () =>
    {
      Time_TaskScheduler.Text = DateTime.Now.ToString("hh:mm:ss");
      return 340;
    });

    // get result from UI thread
    result = reportProgressTask.Result;
    Debug.WriteLine(result);
  }, 
  CancellationToken.None,
  TaskCreationOptions.None,
  TaskScheduler.Default)
    .ConfigureAwait(false)
    .GetAwaiter()
    .GetResult()
    .ContinueWith(_ =>
    {
      var reportProgressTask = ReportProgressTask(taskScheduler, () =>
      {
        Status_TaskScheduler.Text = "Finished";
        return 0;
      });
      reportProgressTask.Wait();
    });
}

/// <summary>
/// 
/// </summary>
/// <param name="taskScheduler"></param>
/// <param name="func"></param>
/// <returns></returns>
private Task<int> ReportProgressTask(TaskScheduler taskScheduler, Func<int> func)
{
  var reportProgressTask = Task.Factory.StartNew(func,
    CancellationToken.None,
    TaskCreationOptions.None,
    taskScheduler);
  return reportProgressTask;
}

As they say, there is more than one way to schedule a task ; )

Eliahu Aaron
  • 4,103
  • 5
  • 27
  • 37
Stacy Dudovitz
  • 952
  • 9
  • 11
6

SynchronizationContext is an abstraction that uses virtual methods. Using SynchronizationContext allows you don't tie your implementation to a specific framework.

Example: Windows Forms uses the WindowsFormSynchronizationContext that overrides Post to call Control.BeginInvoke. WPF uses the DispatcherSynchronizationContext type that overrides Post to call Dispatcher.BeginInvoke. You can design your component that uses SynchronizationContext and doesn't tie implementation to a particular framework.

Eliahu Aaron
  • 4,103
  • 5
  • 27
  • 37
V. S.
  • 1,086
  • 14
  • 14
3

The Dispatcher class is useful when you’re sure that you are calling within the context of the user interface thread and the SynchronizationContext is useful when you're not quite sure.

If you obtain your Dispatcher class using the static Dispatcher.CurrentDispatcher method on some non-UI thread and call the BeginInvoke method, nothing will happen (No exception, no warning, nada).

However, if you obtain your SynchronizationContext class via the static SynchronizationContext.Current method, it will return null if the thread is not a UI thread. This feather is very useful because it allows you to react to both a UI thread and a non-UI thread accordingly.

Gul Ershad
  • 1,743
  • 2
  • 25
  • 32