public void TimerCallback(object state)
{
ThreadInvoker.Instance.RunByUiThread(() =>
{
string text = "Timmer Tick: " + DateTime.Now.ToLongTimeString() + "\n";
var color = Colors.Blue;
var paragraph = new Paragraph(new Run(text))
{ Foreground = new SolidColorBrush(color) };
this.logger.Document.Blocks.Add(paragraph);
});
}
Introduction
This article explains how to simplify the use of threads in WPF for:
- UI threads (using the Dispatcher)
- New threads (using Action\Func invocation)
Background
The use of threads is very common, threads are used for parallelizing your application. Threading enables your application to perform concurrent processing so that you can do more than one operation at a time. You can read more about threads in MSDN Threading Tutorial.
Let's separate our discussion to 2 types of threads:
Please notice: I write UI Threads and not UI Thread! Because there can be more than one UI thread in WPF. For more information, please see the section "Multiple Windows, Multiple Threads" in this MSDN great article: WPF Threading Model.
Back in the days of .NET 1.1, we could access UI objects form any thread, without checking first for cross-thread UI changes. This access caused an unexpected UI behavior.
But .NET 2.0 onward recognizes the attempts when trying to change the UI from non-UI thread, and throws an exception. One can avoid this check, at one's own peril, by setting the CheckForIllegalCrossThreadCalls
flag to false
. For more information, you can read the article: How To Handle Cross-thread Access to GUI Elements.
WPF uses the Dispatcher class for invoking code execution by the UI thread.
.NET enables us to create a new thread in a very basic way: see MSDN article How to: Create Threads.
.NET 3.5 onward simplified the use of new thread by Action\Func invocation. In this article, I'm going to take it to the next level by simplifying the general use of threads for both UI & New Threads.
Simplifying the Use of Threads
From my experience, it is better to unify & simplify the use of threads into a structured utility that can be used across the application for the following reasons:
- Avoids code duplication across the application
- Threads invocation code is unified and can easily be changed
- Ease of access to the use of the object for the less experienced programmers on the team
Thread Invoker Code
First let's present the Thread Invoker code for those of us that just want to copy paste:
public class ThreadInvoker
{
#region Singleton
private ThreadInvoker()
{
}
public static ThreadInvoker Instance
{
get
{
return Nested.instance;
}
}
class Nested
{
static Nested()
{
}
internal static readonly ThreadInvoker instance = new ThreadInvoker();
}
#endregion
static readonly object padlock = new object();
#region New Thread
public void RunByNewThread(Action action)
{
lock (padlock)
{
action.BeginInvoke(ar => ActionCompleted(ar, res => action.EndInvoke(res)), null);
}
}
public void RunByNewThread<TResult>(Func<TResult> func, Action<TResult> callbackAction)
{
lock (padlock)
{
func.BeginInvoke(ar =>
FuncCompleted<TResult>(ar, res => func.EndInvoke(res),callbackAction), null);
}
}
private static void ActionCompleted(IAsyncResult asyncResult,
Action<IAsyncResult> endInvoke)
{
if (asyncResult.IsCompleted)
{
endInvoke(asyncResult);
}
}
private static void FuncCompleted<TResult>(IAsyncResult asyncResult,
Func<IAsyncResult, TResult> endInvoke,
Action<TResult> callbackAction)
{
if (asyncResult.IsCompleted)
{
TResult response = endInvoke(asyncResult);
if (callbackAction != null)
{
callbackAction(response);
}
}
}
#endregion
#region UI Thread
private Dispatcher m_Dispatcher = null;
public void InitDispacter(Dispatcher dispatcher = null)
{
m_Dispatcher = dispatcher == null ? (new UserControl()).Dispatcher : dispatcher;
}
public void RunByUiThread(Action action)
{
#region UI Thread Safety
if (m_Dispatcher.Thread != Thread.CurrentThread)
{
m_Dispatcher.BeginInvoke(DispatcherPriority.Normal, action);
return;
}
action();
#endregion
}
public T RunByUiThread<T>(Func<T> function)
{
#region UI Thread Safety
if (m_Dispatcher.Thread != Thread.CurrentThread)
{
return (T)m_Dispatcher.Invoke(DispatcherPriority.Normal, function);
}
return function();
#endregion
}
#endregion
}
The "Thread Invoker" enables us to simplify the use of threads (using Actions\Funcs) by a simple singleton.
Now, for those of you that really want to understand the code, I'll explain the code in parts:
Let's say we want to run a piece of code by the UI thread. For example, the motivation is the need to update a UI RichTextBox
from a Non-UI thread, for example, Timer CallBack.
First, when using the Thread Invoker "Run by UI thread" capability, we should set the Dispatcher, for most cases we can do it once per application, for example at the MainWindow
constructor:
public MainWindow()
{
InitializeComponent();
ThreadInvoker.Instance.InitDispacter();
m_Timer = new Timer(TimerCallback, null, 1000, 1000);
}
We can see above that we are also setting a timer with 1 sec intervals. Every timer tick we are updating the UI (RichTextBox
) by adding the timer tick time:
protected void TimerCallback(object state)
{
var numberOfChars = ThreadInvoker.Instance.RunByUiThread(() =>
{
string text = "Timmer Tick: " + DateTime.Now.ToLongTimeString() + "\n";
int res = text.Length;
var color = Colors.Blue;
var paragraph = new Paragraph(new Run(text))
{ Foreground = new SolidColorBrush(color) };
this.logger.Document.Blocks.Add(paragraph);
return res; });
WriteToLog("Timer callback number of chars written: " + numberOfChars);
}
The use of the Thread Invoker is very simple, just run the code in an anonymous method as an Action\Func parameter.
In the example above, I've used the anonymous method as a Func parameter because I wanted a return value.
Please notice: If you are using a Func
, as in the example above, the current thread is waiting for the result. But if you are using an action, the current thread will continue and the Action
will be executed at the UI thread free time, as it should be. :)
Let's look behind the scenes to see the implementation:
public T RunByUiThread<T>(Func<T> function)
{
#region UI Thread Safety
if (m_Dispatcher.Thread != Thread.CurrentThread)
{
return (T)m_Dispatcher.Invoke(DispatcherPriority.Normal, function);
}
return function();
#endregion
}
The method receives a Generic Func
, check with the Dispatcher if we are running in a non-UI thread. If so, we are using the Dispatcher to invoke the Func
, else we are just running the Func
. Simple as that.
Let's say we want to run a piece of code by a new thread. For example, the motivation is the need to calculate the Time (Now) by a new thread on a Click event, i.e., from the UI thread, and then presenting the time in the UI (RichTextBox
) by using the UI-Thread:
private void logger_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
ThreadInvoker.Instance.RunByNewThread<int>(() =>
{
string text = "Click Time: " + DateTime.Now.ToLongTimeString() + "\n";
int res = text.Length;
ThreadInvoker.Instance.RunByUiThread(() =>
{
var color = Colors.Red;
var paragraph = new Paragraph(new Run(text))
{ Foreground = new SolidColorBrush(color) };
this.logger.Document.Blocks.Add(paragraph);
});
return res;
}, (res) => { WriteToLog("Mouse click number of chars written: " + res); });
}
Again, the use is very simple, just run the code in an anonymous method as a Action\Func parameter.
In the example above, I've used the anonymous method as a Func
parameter, again, because I wanted to return a value.
Please notice: Since you are using new thread, it is an unsynchronized call! i.e. the current thread continues. If you are using a Func
with a returned value, as in the example above, you can handle the returned value in the call-back:
(res) => { WriteToLog("Mouse click number of chars written: " + res); }
Let's look behind the scenes to see the implementation:
public void RunByNewThread<TResult>(Func<TResult> func, Action<TResult> callbackAction)
{
lock (padlock)
{
func.BeginInvoke(ar => FuncCompleted<TResult>(ar, res => func.EndInvoke(res),
callbackAction), null);
}
}
private static void FuncCompleted<TResult>(IAsyncResult asyncResult,
Func<IAsyncResult, TResult> endInvoke,
Action<TResult> callbackAction)
{
if (asyncResult.IsCompleted)
{
TResult response = endInvoke(asyncResult);
if (callbackAction != null)
{
callbackAction(response);
}
}
}
Here it is a bit more complicated, since the Func
is running in a new thread it's an unsynchronized activity. Getting the returned value can happen only by using a callback action.
The FunCompleted
method gets the returned value & sends it to the callback. The callback can set the returned value into a variable using anonymous Action:
(res) => { WriteToLog("Mouse click number of chars written: " + res); }
So the code flow is independent and the programmer is free from any thread-orientated programming.