Click here to Skip to main content
11,478,066 members (69,236 online)
Click here to Skip to main content

How To: Simplify the Use of Threads in WPF

, 29 Apr 2014 CPOL 8.8K 371 31
Rate this:
Please Sign up or sign in to vote.
Simplifying the use of UI threads (using the Dispatcher) & new threads in WPF

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.

UI Threads

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.

New Threads

.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:

  1. Avoids code duplication across the application
  2. Threads invocation code is unified and can easily be changed
  3. 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
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        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;

    //You have to Init the Dispatcher in the UI thread! 
    // Init once per application (if there is only one Dispatcher).
    public void InitDispacter(Dispatcher dispatcher = null)
    {
        m_Dispatcher = dispatcher == null ? (new UserControl()).Dispatcher : dispatcher;
    }

    public void RunByUiThread(Action action)
    {
        #region UI Thread Safety

        //handle by UI Thread.
        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
        //handle by UI Thread.
        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:

Run by UI Thread

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();

    //You have to Init the Dispatcher in the UI thread! 
    //init once per application (if there is only one Dispatcher).
    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;//return number of chars written.
    });

    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. Smile | :)

Let's look behind the scenes to see the implementation:

public T RunByUiThread<T>(Func<T> function)
{
    #region UI Thread Safety

    //handle by UI Thread.
    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.

Run by New Thread

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Shai Vashdi
Chief Technology Officer GalilCS
Israel Israel
My name is Shai Vashdi and I’m the CTO & Co-Founder of GalilCS.
GalilCS provides industrialized, low-cost IT services (ILCS) to corporations around the globe. We specialize in code modernization projects (VB6 to C# WinForms/HTML5, for example) and code refactoring projects.
Most of my work revolves around the management of these projects and the creation of new tools for refactoring code. As a bonus, I also get to lecture about Microsoft programming languages.
For more information, visit GalilCS.
Follow on   Twitter   LinkedIn

Comments and Discussions

 
GeneralMy vote of 5 Pin
Member 104747566-May-14 23:51
memberMember 104747566-May-14 23:51 
Questionlock? Pin
FatCatProgrammer30-Apr-14 5:20
memberFatCatProgrammer30-Apr-14 5:20 
AnswerRe: lock? Pin
Shai Vashdi1-May-14 1:13
memberShai Vashdi1-May-14 1:13 
GeneralRe: lock? Pin
FatCatProgrammer1-May-14 9:37
memberFatCatProgrammer1-May-14 9:37 
GeneralRe: lock? Pin
Shai Vashdi1-May-14 10:54
memberShai Vashdi1-May-14 10:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150520.1 | Last Updated 29 Apr 2014
Article Copyright 2014 by Shai Vashdi
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid