Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / Visual Basic

Building a Class that Raises Events in a Specific or UI Thread

Rate me:
Please Sign up or sign in to vote.
4.57/5 (6 votes)
8 Mar 2013CPOL6 min read 79.3K   1.7K   35   7
A BackgroundWorkerThreadSafe Class in C# and VB.Net

Introduction

This is not a tutorial on threading or thread safety; there are too many of those available out there. If you have never used Invokes or do not know about thread contexts or synchronization, please start with an internet search of "Avoiding InvokeRequired". There is an excellent code project article written by Pablo Grisafi that should bring you up to speed.  You can also do a search for "ISynchronizeInvoke" or "Synchronization Context" you will find countless articles to educate you. What you will not find in any internet search is the code presented in this article (not until some coders pick it up from here and spread it around).

Terminology 

If you are not a beginner, skip this section. Or if you are knowledgeable but like to be amused read it. 

My dad used to tell me to "read a book" when he could save me the time with a quick answer. So in the spirit of how that annoyed me; I will try to give beginners a very simplistic overview and hope there are no arrogant skilled individuals that want to post comments to make themselves look brilliant by pointing out omissions and fallibleness due to brevity.  

When you write source code you are likely to use an IDE (Integrated Development Environment). One example would be Visual Studio 2012. However many coders may choose to write their source code without an IDE and compile the source code from the command line. They may have custom source code editors and compiler tools they developed for themselves to save them time and keystrokes. The IDE will help you write and manage your source code and provide easy ways to compile your source code. In the case of Visual Studio 2012 several compilers are included. The most common compiler for managed code is C# also known as CSharp. Another popular managed language is VB.Net also known as Visual Basic dot Net. 

When you create a simplistic windows application (program) you create a form for people to use. This is the same statement as you create a form for the user to interface with. The form is also known as the user interface. Notice how the terms we use are sensible; with a little intelligent deduction you can see how a user interface is a part of a program that a person uses. Or put another way; the user interfaces with.   And just as in real life; nothing is ever truly that simple. A program could contain several user interfaces. It is important to remember inheritance in modern object oriented programming (oop).   A program may contain several user interfaces however each user interface will instantiate it's own objects. Instantiate means create. Creating an object is done by calling it's constructor. In VB.Net every constructor is "Sub New" in  C# it is a subroutine with the same name as the class. Note: In managed languages such as VB.Net and C# creating or calling the destructor is unnecessary and unwanted. Once an object is instantiated (created) you now have an instance of that object. The source code for a class we write is the "meta data" for our object.  Only an operating thread during runtime can create (instantiate) our objects (classes) for us and at that point our "instances" have a "context". In the case of multiple threads in an application (background workers, etc.) we have to ensure that instances from one thread do not call methods of instances of another thread. This is also known as not being thread safe. When we have a background thread raise an event (also known as firing an event, also known as invoking a method)  we may want to avoid cross threading issues by simply "Invoking the event so that the thread that instantiated it's object instance is the thread that executes the Invoked Method."

This is the most help I can give you on the subject without performing surgery that implants the knowledge directly into your brain.

Background 

When I first discovered the BackgroundWorker class I thought to myself; "Great, a nice convenient way to fire and forget when I need to implement some background processing."  Although it is a given that the method handled by the DoWork event would run in a separate thread and inherently not be thread safe. I assumed that the ProgressChanged event would be thread safe for my main thread. I mean why else make such a class if that is not the case?  And it turns out it was not the case!

The problem is that occasionally the method Invoked by the ProgressChanged event will sometimes not be executed in the thread that instantiated the method's object instance. 

The solution is to raise the event by explicitly specifying the thread or automatically detecting the UIThread and specifying that the method be executed on that thread. So I created a class that is more intuitive to what I believe BackgroundWorker should have been in the first place.

Cross threading is not pretty and not always easy to avoid. If you are a beginner; simply use the BackgroundWorkerThreadSafe class without diving into the code. If you are intermediate to advanced; dive right in and make changes; suggestions are always welcome. And there is much room for suggesting ideas on how to fire events maintaining thread safety.

Using the code 

BackgroundWorkerThreadSafe is a short and simple class. It inherits BackgroundWorker, overrides OnProgressChanged and OnRunWorkerCompleted, and raises those events utilizing a shared method RaiseEventAndExecuteItInAnExplicitOrUIThread; which is the core of the code for discussion here. 

For VB.Net coders the class is posted below:  

VB.NET
Public Class BackgroundWorkerThreadSafe
'System.Windows.Threading.Dispatcher may require you
'to Add a Reference to "WindowsBase" (yes that is a Dot.Net library)
    Inherits System.ComponentModel.BackgroundWorker
    Public Shadows Event ProgressChanged(ByVal sender As Object, _
           ByVal e As System.ComponentModel.ProgressChangedEventArgs)
    Public Shadows Event RunWorkerCompleted(ByVal sender As Object, _
           ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs)
    Private m_ExplicitDispatcher As System.Windows.Threading.Dispatcher = Nothing
 
    Protected Overrides Sub OnProgressChanged(ByVal e As System.ComponentModel.ProgressChangedEventArgs)
        RaiseEventAndExecuteItInAnExplicitOrUIThread(ProgressChangedEvent, New Object() {Me, e}, m_ExplicitDispatcher)
    End Sub
 
    Protected Overrides Sub OnRunWorkerCompleted(ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs)
        RaiseEventAndExecuteItInAnExplicitOrUIThread(RunWorkerCompletedEvent, New Object() {Me, e}, m_ExplicitDispatcher)
    End Sub
 
    Public Sub New(ByVal ExplicitDispatcher As System.Windows.Threading.Dispatcher)
        MyBase.New()
        m_ExplicitDispatcher = ExplicitDispatcher
    End Sub
 
    Private Shared Sub RaiseEventAndExecuteItInAnExplicitOrUIThread(ByVal _event _
            As System.MulticastDelegate, ByVal _ParamArray_args() As Object, _
            ByVal ExplicitThreadSynchronizationDispatcher As System.Windows.Threading.Dispatcher)
        If Not _event Is Nothing Then
            If _event.GetInvocationList().Length > 0 Then
                Dim _sync As System.ComponentModel.ISynchronizeInvoke = Nothing
                For Each _delegate As System.MulticastDelegate In _event.GetInvocationList()
                    If ((ExplicitThreadSynchronizationDispatcher Is Nothing) AndAlso (_sync Is Nothing) _
                         AndAlso (GetType(System.ComponentModel.ISynchronizeInvoke).IsAssignableFrom(_
                         _delegate.Target.GetType())) AndAlso (Not _delegate.Target.GetType().IsAbstract)) Then
                        Try
                            _sync = CType(_delegate.Target, System.ComponentModel.ISynchronizeInvoke)
                        Catch ex As Exception
                            Diagnostics.Debug.WriteLine(ex.ToString())
                            _sync = Nothing
                        End Try
                    End If
                    If Not ExplicitThreadSynchronizationDispatcher Is Nothing Then
                        Try
                            ExplicitThreadSynchronizationDispatcher.Invoke(_delegate, _ParamArray_args)
                        Catch ex As Exception
                            Diagnostics.Debug.WriteLine(ex.ToString())
                        End Try
                    Else
                        If _sync Is Nothing Then
                            Try
                                _delegate.DynamicInvoke(_ParamArray_args)
                            Catch ex As Exception
                                Diagnostics.Debug.WriteLine(ex.ToString())
                            End Try
                        Else
                            Try
                                _sync.Invoke(_delegate, _ParamArray_args)
                            Catch ex As Exception
                                Diagnostics.Debug.WriteLine(ex.ToString())
                            End Try
                        End If
                    End If
                Next
            End If
        End If
    End Sub
End Class

For C# coders, the class is posted below:

C#
public class BackgroundWorkerThreadSafe : System.ComponentModel.BackgroundWorker
//System.Windows.Threading.Dispatcher may require you to
//Add a Reference to "WindowsBase" (yes that is a Dot.Net library)
{
    public new event ProgressChangedEventHandler ProgressChanged;
    public delegate void ProgressChangedEventHandler(object sender, System.ComponentModel.ProgressChangedEventArgs e);
    public new event RunWorkerCompletedEventHandler RunWorkerCompleted;
    public delegate void RunWorkerCompletedEventHandler(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e);
 
    private System.Windows.Threading.Dispatcher m_ExplicitDispatcher = null;
    protected override void OnProgressChanged(System.ComponentModel.ProgressChangedEventArgs e)
    {
        RaiseEventAndExecuteItInAnExplicitOrUIThread(ProgressChanged, new object[] {
			this,
			e
		}, m_ExplicitDispatcher);
    }
 
    protected override void OnRunWorkerCompleted(System.ComponentModel.RunWorkerCompletedEventArgs e)
    {
        RaiseEventAndExecuteItInAnExplicitOrUIThread(RunWorkerCompleted, new object[] {
			this,
			e
		}, m_ExplicitDispatcher);
    }
 
    public BackgroundWorkerThreadSafe(System.Windows.Threading.Dispatcher ExplicitDispatcher)
        : base()
    {
        m_ExplicitDispatcher = ExplicitDispatcher;
    }
 
    private static void RaiseEventAndExecuteItInAnExplicitOrUIThread(System.MulticastDelegate 
      _event, object[] _ParamArray_args, System.Windows.Threading.Dispatcher ExplicitThreadSynchronizationDispatcher)
    {
        if ((_event != null))
        {
            if (_event.GetInvocationList().Length > 0)
            {
                System.ComponentModel.ISynchronizeInvoke _sync = null;
                foreach (System.MulticastDelegate _delegate in _event.GetInvocationList())
                {
                    if (((ExplicitThreadSynchronizationDispatcher == null) && (_sync == null) && 
                      (typeof(System.ComponentModel.ISynchronizeInvoke).IsAssignableFrom(_delegate.Target.GetType())) && 
                      (!_delegate.Target.GetType().IsAbstract)))
                    {
                        try
                        {
                            _sync = (System.ComponentModel.ISynchronizeInvoke)_delegate.Target;
                        }
                        catch (System.Exception ex)
                        {
                            System.Diagnostics.Debug.WriteLine(ex.ToString());
                            _sync = null;
                        }
                    }
                    if ((ExplicitThreadSynchronizationDispatcher != null))
                    {
                        try
                        {
                            ExplicitThreadSynchronizationDispatcher.Invoke(_delegate, _ParamArray_args);
                        }
                        catch (System.Exception ex)
                        {
                            System.Diagnostics.Debug.WriteLine(ex.ToString());
                        }
                    }
                    else
                    {
                        if (_sync == null)
                        {
                            try
                            {
                                _delegate.DynamicInvoke(_ParamArray_args);
                            }
                            catch (System.Exception ex)
                            {
                                System.Diagnostics.Debug.WriteLine(ex.ToString());
                            }
                        }
                        else
                        {
                            try
                            {
                                _sync.Invoke(_delegate, _ParamArray_args);
                            }
                            catch (System.Exception ex)
                            {
                                System.Diagnostics.Debug.WriteLine(ex.ToString());
                            }
                        }
                    }
                }
            }
        }
    }
}

As I stated, there are too many tutorials and references on the net for me to be writing duplicate information here. What you will not find with a search is the shared method noted in this article.

The demo projects included in both VB.NET and C# are simple windows applications showing a full example on how to use the class. The examples will be the same for the original BackGroundWorker except the constructor takes one parameter that can either be Nothing or null; or it can be a Systems.Windows.Threading.Dispatcher for explicitly setting which thread the methods handled by the events are executed within. If the parameter is Nothing or null then the System.ComponentModel.ISynchronizeInvoke of the delegate target will be used to determine the thread that instantiated BackgroundworkerThreadSafe and if no interface is found it will use the delegates DynamicInvoke method to raise the event normally. 

Points of Interest

Because the code uses Systems.Windows.Threading.Dispatcher it is necessary to add a reference to WindowsBase if anyone knows how to replace that functionality utilizing System.Threading.SynchronizationContext, please let us know. 

History 

As readers can see the code is not overwhelming in raw size. Why Microsoft did not integrate this or something similar in the original class defies common sense. In any case;  enjoy and write comments! 

License

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


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
BugTypo in your sample Form1.cs Pin
BruceCarson1-May-13 5:23
BruceCarson1-May-13 5:23 

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

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