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:
Public Class BackgroundWorkerThreadSafe
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:
public class BackgroundWorkerThreadSafe : System.ComponentModel.BackgroundWorker
{
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!