Friday, November 30, 2007

Dispatcher versus SynchronizationContext

Today I had the pleasure of playing with the Dispatcher and SynchronizationContext classes. I found that the Dispatcher class is useful when your 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 feedback is extremely useful since it allows you to react to both a UI thread and a non-UI thread accordingly.

Here is an example using the Dispatcher class:


using System;
using System.Threading;
using System.Windows.Threading; // WindowsBase.dll

namespace Test
{
public delegate void WriteTextDelegate(string message);

public class DispatcherTest
{
public void Start()
{
// Calling thread determines Dispatcher
m_Dispatcher = Dispatcher.CurrentDispatcher;

if (m_Timer == null)
m_Timer = new Timer(new TimerCallback(WriteText),
null, 1000, 2000);
else m_Timer.Change(1000, 2000);
}

public void Stop()
{
if (m_Timer != null)
m_Timer.Change(Timeout.Infinite, Timeout.Infinite);
}

public event WriteTextDelegate Message;

private Timer m_Timer;
private System.Windows.Threading.Dispatcher m_Dispatcher;

private void WriteText(object state)
{
m_Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.Normal,
new WriteTextDelegate(OnMessage),
DateTime.Now.ToString());
}

private void OnMessage(string message)
{
if (Message != null)
Message(message);
}
}
}



Here is an example using the SynchronizationContext class:

using System;
using System.Threading;

namespace Test
{
public delegate void WriteSomeTextDelegate(string message);

public class SyncTest
{
public void Start()
{
// Calling thread determines SynchronizationContext
m_Context = SynchronizationContext.Current;

if (m_Timer == null)
m_Timer = new Timer(new TimerCallback(WriteText),
null, 1000, 2000);
else m_Timer.Change(1000, 2000);
}

public void Stop()
{
if (m_Timer != null)
m_Timer.Change(Timeout.Infinite, Timeout.Infinite);
}

public event WriteSomeTextDelegate Message;

private System.Threading.Timer m_Timer;
private System.Threading.SynchronizationContext m_Context;

private void WriteText(object state)
{
string message = DateTime.Now.ToString();

if (m_Context != null)
{
m_Context.Post(new SendOrPostCallback(PostCallback), message);
}
else OnMessage(message); // non-UI thread called Start
}

private void OnMessage(string message)
{
if (Message != null)
Message(message);
}

private void PostCallback(object state)
{
if (state is string)
{
OnMessage(state as string);
}
else throw new ArgumentException("State should be a string");

}
}
}


Notes:
  • I used Reflector to look inside the WindowsBase.dll; however, I had problems finding the dll. I expected to find it in a directory below the usual location (C:\ Windows\ Microsoft.NET\ Framework\ v3.0); however, it was in this directory: C:\ Program Files\ Reference Assemblies\ Microsoft\ Framework\ v3.0
  • WCF: Asynchronous Operations

2 comments:

Marcin Rybacki said...

Just, what I was looking for, thanks :)

Dimitry Zusman said...

You can check UI thread via Dispatcher too:
Dispatcher dispatcher = Dispatcher.FromThread(Thread.CurrentThread);
// if not in ui thread
if (dispatcher != null)