Windows Forms Threading
(by Joe Williams for Blayd Software)
Note: this article relates to the .Net Framework version 2.0, some
of the classes and class members referenced in this article were not available in
earlier framework versions.
Introduction
Windows Forms applications are designed and developed to interact with a user, the
interaction may take many forms and may be for many different reasons. For example,
an application may display data in a useful and informative way, capture and validate
data input from a user, provide access to functions or services that process data
or provide all of these features and possibly more. Designing an intuitive and effective
user interface for a Windows Forms application is a complex and skilled process
and there are many objectives that have to be fulfilled. One of the key objectives
is to provide a responsive application, users generally don't like waiting around
while an application is retrieving data or performing calculations, for example.
An application that requires user interaction must react to the user's activities
as rapidly as possible to provide a rich user experience. At the same time, however,
an application often has to perform the calculations and formatting necessary to
present data to the user as quickly as possible.
When developing a Windows Forms application we want the application to be as responsive
as possible when interacting with the user, even if the application is currently
performing other work. Multithreading (using multiple threads of execution) is one
way to keep an application responsive to the user whilst making use of the processor
in between or even during user activated events. Using more than one thread is an
effective technique to increase the responsiveness of the user interface and process
data at the same time or almost the same time on a single processor (single core)
machine. On a machine with multiple processors or a multi-core processor, that can
run more than one thread concurrently, the improvement in user interface response
can be quite dramatic and even on a machine with one (single core) processor, multiple
threads can create the same effect by taking advantage of the periods of time between
user activated events to process data in the background.
In this article we will be discussing several techniques that can be used to run
a task on a background or worker thread in a Windows Forms application. We will
also be discussing asynchronous methods and showing how they can be used in classes
or components that provide services, either from within the same assembly or from
a class library, for a Windows Forms application.
Interacting with the User Interface from a Background Thread
Before we start discussing creating and running background threads and implementing
asynchronous methods there is one important aspect of multithreading in Windows
Forms applications that has to be appreciated. Controls, including Form, in Windows
Forms operate in a single-threaded apartment (STA) because Windows Forms is based
on native Win32 widows that are apartment threaded. The STA threading model allows
a window to be created on any thread, however, it cannot switch threads once it
is created and all function calls to the window must occur on the thread on which
it was created. Therefore, in a standard Windows Forms application, all forms and
controls, typically, execute on the same thread, the primary or main UI thread on
which the application was launched. Each control is bound to the primary thread,
which runs its message pump and cannot be updated from a different thread, therefore,
controls in Windows Forms are not thread-safe. If you try to update a control, for
example, by setting its Text property from a thread other than the main UI thread
you may force the control into an inconsistent state. The update may work or it
may not, you may get thread-related bugs such as race conditions or deadlocks or
in the worst case the application may hang or crash.
If the application is running in the debugger from within Visual Studio 2005 attempted
illegal cross thread calls, such as the one described above, will be intercepted
by the debugger and an InvalidOperationException will be thrown. The illegal cross
thread call exception is documented as follows "This exception occurs reliably during
debugging and, under some circumstances, at run time. You are strongly advised to
fix this problem when you see it".
Background threads that do not interact with the user interface do not present a
problem, however, it is a common requirement for a background task to inform the
user of the of the task's progress, by updating a progress bar control, for example,
during the execution of the task. Therefore a reliable, thread-safe, way of updating
a user interface control from a background thread is required. The Control base
class provides three thread-safe methods BeginInvoke, EndInvoke and
Invoke that can be used to interact with a user interface control from a background thread,
it also provides the InvokeRequired property that can be used to determine whether
the caller is on a different thread from the one the control was created on.
The code sample below shows how to update a user interface control from a background
thread, the code to create the background thread and sink the event has been omitted
for clarity:
C# user interface control update from
a background thread
internal partial class Form1 : Form
{
private delegate void ProgressUpdate(ProgressChangedEventArgs e);
// Updates the progress bar.
private void UpdateProgress(ProgressChangedEventArgs e)
{
if (e != null)
{
progressBarAsync.Value = e.ProgressPercentage;
}
}
// Handles the ProgressChanged event.
private void AsyncProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (this.InvokeRequired)
{
// Use Control.Invoke to marshal the data to the UI thread and
// call the UpdateProgress method on the UI thread...
this.Invoke(new ProgressUpdate(this.UpdateProgress), new object[] { e });
}
else
{
// On the correct thread, so can make a direct call...
UpdateProgress(e);
}
}
}
VB user interface control update from a
background thread
Friend Class Form1
Private Delegate Sub ProgressUpdate(ByVal e As ProgressChangedEventArgs)
' Updates the progress bar.
Private Sub UpdateProgress(ByVal e As ProgressChangedEventArgs)
If (e IsNot Nothing) Then
progressBarAsync.Value = e.ProgressPercentage
End If
End Sub
' Handles the ProgressChanged event.
Private Sub AsyncProgressChanged(ByVal sender As Object, _
ByVal e As ProgressChangedEventArgs)
If (Me.InvokeRequired) Then
' Use Control.Invoke to marshal the data to the UI thread and
' call the UpdateProgress method on the UI thread...
Me.Invoke(New ProgressUpdate(AddressOf Me.UpdateProgress), New Object() {e})
Else
' On the correct thread, so can make a direct call...
UpdateProgress(e)
End If
End Sub
End Class
The code sample first creates a private delegate which will be passed to the Control.Invoke
method and used to call the UpdateProgress method on the main UI thread. The delegate
signature must match the signature of the user interface control update method.
The AsyncProgressChanged method represents the ProgressChanged event handler that
will be called on a background thread. The handler first checks if the call is on
the same thread as the control that is to be updated, in this example we can just
use the form's inherited InvokeRequired property as we know that the progress bar
control is running on the same, main UI, thread as the form. If the call is on a
different thread, the code calls the Control.Invoke method (we can use the Form
as we did for InvokeRequired) passing in an instance of the ProgressUpdate delegate
and the event data as the only element in an Object array. The Object array represents
the arguments of the method that is to be called by the delegate, therefore the
UpdateProgress method will be passed the ProgressChangedEventArgs instance when
it is called by the delegate. The Control.Invoke method marshals the data to the
main UI thread and then invokes the delegate on the main UI thread. Because we know
that the UpdateProgress method will always be called on the main UI thread we can
go ahead and update the progress bar control.
Threading Options
If your threading requirements are fairly straightforward we strongly recommend
that you consider using the System.ComponentModel.BackgroundWorker component rather
than explicitly creating and managing your own threads. We will be discussing the
BackgroundWorker component in detail later in the article so we won't go into too
much detail here. However, when choosing which of the threading options is best
for your situation one of the main benefits of the BackgroundWorker component is
that it implements the Event Based Asynchronous Design Pattern which can make UI
control updates a lot easier.
The BackgroundWorker component has three public events that you should
handle in your code:
- The
DoWork event
-
This is the event from which you should run your background task. The
DoWork event
handler is called on a background thread and should only be used to run the background
task, you should not update UI controls from within the event handler or from within
methods that are called from the event handler.
- The
ProgressChanged event
-
You can raise this event from within your background task by calling one of the
BackgroundWorker.ReportProgress method overloads. Internally the BackgroundWorker
component uses the System.ComponentModel.AsyncOperationManager class to marshal
the data and then raise the ProgressChanged event on the appropriate thread. Therefore,
if the BackgroundWorker component was created on the main UI thread and you handle
the ProgressChanged event on the main UI thread, you can update UI controls directly
from within your handler code.
- The
RunWorkerCompleted event
-
This event is raised by the
BackgroundWorker component when the DoWork handler code
returns i.e. when the background task has completed, been cancelled or has encountered
an error. Like the ProgressChanged event the RunWorkerCompleted event handler will
be called on the main UI thread, if the BackgroundWorker component was created on
the main UI thread, therefore you can update UI controls directly from within your
handler code.
See the BackgroundWorker Component section for more details on using the
BackgroundWorker component.
The .Net Framework provides two options for developers who want to create and/or
manage their own background or worker threads in an application. You can use the
System.Threading.Thread class to create your own thread or threads or you can use
the static System.Threading.ThreadPool class to retrieve a thread or threads from
the thread pool.
The ThreadPool class provides an application with a pool of worker threads that
are managed by the system. There is one thread pool per process and by default it
contains 25 worker threads per processor. Thread pool threads are background threads
i.e. their Thread.IsBackground property is set to True, therefore, a thread pool
thread will not keep an application running after all foreground threads have exited.
For an application that has short tasks that require background processing, the
managed thread pool provides an efficient and easy way to utilise multiple threads.
The code sample below shows how to run a background task on a thread from the thread
pool:
C# background task running on a thread
from the thread pool
private void BackgroundTask(object state)
{
// Threading method, do NOT update UI controls...
Console.WriteLine("BackgroundTask running on thread: {0}",
Thread.CurrentThread.ManagedThreadId);
}
private void buttonThreadPool_Click(object sender, EventArgs e)
{
Console.WriteLine("Main UI thread: {0}", Thread.CurrentThread.ManagedThreadId);
try
{
string arg = "ThreadPool demo.";
if (!ThreadPool.QueueUserWorkItem(new WaitCallback(BackgroundTask), arg))
{
MessageBox.Show("Failed to queue work item.", this.Text);
}
}
catch (OutOfMemoryException ex)
{
MessageBox.Show(ex.Message, this.Text);
}
}
VB background task running on a thread from
the thread pool
Private Sub BackgroundTask(ByVal state As Object)
' Threading method, do NOT update UI controls...
Console.WriteLine("BackgroundTask running on thread: {0}", _
Thread.CurrentThread.ManagedThreadId)
End Sub
Private Sub buttonThreadPool_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles buttonThreadPool.Click
Console.WriteLine("Main UI thread: {0}", Thread.CurrentThread.ManagedThreadId)
Try
Dim arg As String = "ThreadPool demo."
If (Not ThreadPool.QueueUserWorkItem( _
New WaitCallback(AddressOf Me.BackgroundTask), arg)) Then
MessageBox.Show("Failed to queue work item.", Me.Text)
End If
Catch ex As OutOfMemoryException
MessageBox.Show(ex.Message, Me.Text)
End Try
End Sub
The sample code is contained in a Form class and consists of a Button.Click event
handler that triggers the background task on a thread from the thread pool and the
BackgroundTask method which represents the background task that is to run on a thread
from the thread pool. ThreadPool is a static class and therefore all of the code
in your application will use the same thread pool. Because your application has
access to only one thread pool you should be careful when using the thread pool
for really long running tasks or tasks that are likely to block frequently or for
long periods, as a thread pool thread cannot be reused until it has finished its
work (see the Locking, Blocking, Racing and Deadlocking section). You can set the
minimum (idle) and the maximum number of threads on the ThreadPool class, however,
you should use caution when changing the minimum number of idle threads and/or the
maximum number of threads in the thread pool. Whilst your code might benefit, the
changes might have an adverse effect on code libraries used by your application.
A thread pool thread is requested by calling the ThreadPool.QueueUserWorkItem method,
passing in a WaitCallback delegate and an Object that represents the argument for
the background task method. The WaitCallback delegate represents the method that
is to be executed on a thread pool thread. There is a QueueUserWorkerItem method
overload that does not require the Object argument, however, it does still require
a WaitCallback delegate and the WaitCallback delegate signature requires an Object
state parameter, so using this overload is the same as passing a null argument to
the background task method. The QueueUserWorkItem method returns True if the delegate
is successfully queued or False if not. The delegate will be queued by the ThreadPool
class until an idle thread becomes available, therefore, it is not safe to assume
that your background task will be called immediately, nor is it safe to assume that
your delegate will be the only item in the queue. As soon as a thread becomes available,
the ThreadPool class will use it to invoke the delegate and execute the background
task method, passing in the argument Object, if one was specified or null if not.
The Thread class provides the maximum flexibility for running a task or tasks on
a dedicated thread and is not particularly difficult to use. The flexibility of
the Thread class derives from the fact that you can use its properties and methods
to setup the thread to match your requirements, however, to do this you really need
to know what you are doing. In most situations a thread retrieved from the thread
pool or the thread used by an asynchronous delegate (which is a thread pool thread!)
is perfectly adequate and you do not have to explicitly set it up or start it.
Some of the situations that may require the use of the Thread class, rather than
the ThreadPool class are as follows:
- You require a foreground, rather than a background thread.
- You need to set the thread priority.
- You have a task or tasks that cause the thread to block frequently or for long
periods of time. The thread pool has a maximum number of threads, so a large number
of blocked thread pool threads might prevent other tasks from starting.
- You need to have a specified and stable identity associated with the thread or
you need to dedicate a specific thread to a specific task.
- You need to place the thread into a single-threaded apartment (STA). All
ThreadPool
threads are in a multithreaded apartment.
The code sample below shows how to run a background task using and instance of the
Thread class:
C# background task running on an explicitly
created thread
private void BackgroundTask(object state)
{
// Threading method, do NOT update UI controls...
Console.WriteLine("BackgroundTask running on thread: {0}",
Thread.CurrentThread.ManagedThreadId);
}
private void buttonThread_Click(object sender, EventArgs e)
{
Console.WriteLine("Main UI thread: {0}", Thread.CurrentThread.ManagedThreadId);
string arg = "Thread demo.";
Thread worker = new Thread(new ParameterizedThreadStart(BackgroundTask));
worker.IsBackground = true;
worker.Start(arg);
// On a single processor (single core) system the main thread
// may need to yield to allow the background thread to run...
//Thread.Sleep(0);
// Wait for the background thread to finish...
worker.Join();
Console.WriteLine("Done...");
}
VB background task running on an explicitly
created thread
Private Sub BackgroundTask(ByVal state As Object)
' Threading method, do NOT update UI controls...
Console.WriteLine("BackgroundTask running on thread: {0}", _
Thread.CurrentThread.ManagedThreadId)
End Sub
Private Sub buttonThread_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles buttonThread.Click
Console.WriteLine("Main UI thread: {0}", Thread.CurrentThread.ManagedThreadId)
Dim arg As String = "Thread demo."
Dim worker As Thread = New Thread( _
New ParameterizedThreadStart(AddressOf BackgroundTask))
worker.IsBackground = True
worker.Start(arg)
' On a single processor (single core) system the main thread may need
' to yield to allow the background thread to run...
'Thread.Sleep(0)
' Wait for the background thread to finish...
worker.Join()
Console.WriteLine("Done...")
End Sub
The sample code is contained in a Form class and consists of a Button.Click event
handler that triggers the background task on an explicitly created thread and the
BackgroundTask method which represents the background task that is to run on the
thread. The ParameterizedThreadStart delegate is used to allow a state or argument
Object to be passed to the Thread.Start(Object) method. If a state or argument Object
is not needed, you can use the ThreadStart delegate and then call the parameterless
Thread.Start method overload. If the parameterless overload of the Start method
is used and the thread was created using a ParameterizedThreadStart delegate a null
reference will be passed to the method executed by the thread. When using the Thread
class we can setup the thread before starting it, in the sample code we have specified
that we require a background thread by setting the Thread.IsBackground property
to True. A background thread does not prevent a process from terminating. When all
foreground threads belonging to a process have terminated, the common language runtime
ends the process. Any remaining background threads are stopped and do not complete.
This can be a problem in some situations, so we have added a call to the Thread.Join
method to demonstrate how to force the calling (main UI) thread to wait for the
background thread to finish. This does, at first, seem to be counter productive,
however if you are trying to cancel a background task because the application has
been closed by the user, this technique allows the background thread to close cleanly,
providing that you can request it to stop or cancel, rather than aborting. The sample
also includes a commented out call to the Thread.Sleep method, which you may need
to reinstate if you are running the sample on a single processor, single core, machine.
This call makes the calling (main UI) thread yield so that the background thread
can run. Finally we have added an output the to Console to indicate when the background
task has completed and when, therefore, you can close the sample.
Locking, Blocking, Racing and Deadlocking
Queuing a work item in the thread pool or calling the Thread.Start method does not
mean that the background task will start to execute immediately. If several threads
are created to run background tasks they will not, necessarily, actually start in
the order in which they were created and started. You cannot be sure how much code
will be executed in a thread's time slice. If you consider the previous statements
you will soon realise that running extra threads in an application may not be as
straightforward as it seems. A lot of background tasks rely on and alter class level
state fields, either directly or through properties, if a state field can be altered
by multiple threads, perhaps concurrently, then a way is needed to control or synchronize
thread access to the field to ensure that only one thread at time is allowed to
alter the field. Sections of code that alter a resource, such as a class level field,
that is shared between threads are known as critical sections, thread access to
critical sections of code must be synchronized.
The code sample below shows a class level field being updated by two threads. The
sample output shown below was generated on a dual processor machine:
C# multithread shared field update
private int _count;
private void AdjustCount()
{
Console.WriteLine("AdjustCount entered on thread: {0}",
Thread.CurrentThread.ManagedThreadId);
for (int iteration = 0; iteration < 6; iteration++)
{
Console.WriteLine("Count = {0} on thread: {1}", _count,
Thread.CurrentThread.ManagedThreadId);
if (_count % 2 > 0)
{
// Decrement to a value of 0
_count--;
}
else
{
// Increment to a value of 1
_count++;
}
Thread.Sleep(1000);
}
Console.WriteLine("AdjustCount exited on thread: {0}",
Thread.CurrentThread.ManagedThreadId);
}
private void buttonNonSync_Click(object sender, EventArgs e)
{
Thread t1 = new Thread(new ThreadStart(this.AdjustCount));
t1.Start();
Thread t2 = new Thread(new ThreadStart(this.AdjustCount));
t2.Start();
}
VB multithread shared field update
Private _count As Integer
Private Sub AdjustCount()
Console.WriteLine("AdjustCount entered on thread: {0}", _
Thread.CurrentThread.ManagedThreadId)
For iteration As Integer = 0 To 5
Console.WriteLine("Count = {0} on thread: {1}", _count, _
Thread.CurrentThread.ManagedThreadId)
If (_count Mod 2 > 0) Then
' Decrement to a value of 0
_count -= 1
Else
' Increment to a value of 1
_count += 1
End If
Thread.Sleep(1000)
Next
Console.WriteLine("AdjustCount exited on thread: {0}", _
Thread.CurrentThread.ManagedThreadId)
End Sub
Private Sub buttonNonSync_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles buttonNonSync.Click
Dim t1 As Thread = New Thread(New ThreadStart(AddressOf Me.AdjustCount))
t1.Start()
Dim t2 As Thread = New Thread(New ThreadStart(AddressOf Me.AdjustCount))
t2.Start()
End Sub
The sample code above generates the following output
AdjustCount entered on thread: 11
Count = 0 on thread: 11
AdjustCount entered on thread: 12
Count = 1 on thread: 12
Count = 0 on thread: 12
Count = 0 on thread: 11
Count = 0 on thread: 12
Count = 0 on thread: 11
Count = 0 on thread: 12
Count = 0 on thread: 11
Count = 0 on thread: 12
Count = 0 on thread: 11
Count = 0 on thread: 12
Count = 0 on thread: 11
AdjustCount exited on thread: 12
AdjustCount exited on thread: 11
The output shows that the threads t1 and t2 are both executing and changing the
_count field at the same time. It is pretty clear from the code in the AdjustCount
method that this is not what is intended, rather than alternating between 0 and
1, as expected, one thread (eventually) cancels out the other thread and _count is
reported as remaining at 0. To get the AdjustCount method to work as intended we
need to synchronize access to the loop, so that only one thread at a time is allowed
inside the loop.
The code sample below shows a synchronized version of the AdjustCount method:
C# synchronized multithread shared field
update
private int _count;
private object _lockTarget = new object();
private void AdjustCountSync()
{
lock (_lockTarget)
{
Console.WriteLine("AdjustCountSync entered on thread: {0}",
Thread.CurrentThread.ManagedThreadId);
for (int iteration = 0; iteration < 6; iteration++)
{
Console.WriteLine("Count = {0} on thread: {1}", _count,
Thread.CurrentThread.ManagedThreadId);
if (_count % 2 > 0)
{
// Decrement to a value of 0
_count--;
}
else
{
// Increment to a value of 1
_count++;
}
Thread.Sleep(1000);
}
Console.WriteLine("AdjustCountSync exited on thread: {0}",
Thread.CurrentThread.ManagedThreadId);
}
}
VB synchronized multithread shared field
update
Private _count As Integer
Private _lockTarget As Object = New Object()
Private Sub AdjustCountSync ()
SyncLock _lockTarget
Console.WriteLine("AdjustCountSync entered on thread: {0}", _
Thread.CurrentThread.ManagedThreadId)
For iteration As Integer = 0 To 5
Console.WriteLine("Count = {0} on thread: {1}", _count, _
Thread.CurrentThread.ManagedThreadId)
If (_count Mod 2 > 0) Then
' Decrement to a value of 0
_count -= 1
Else
' Increment to a value of 1
_count += 1
End If
Thread.Sleep(1000)
Next
Console.WriteLine("AdjustCountSync exited on thread: {0}", _
Thread.CurrentThread.ManagedThreadId)
End SyncLock
End Sub
The sample code above generates the following output
AdjustCountSync entered on thread: 12
Count = 0 on thread: 12
Count = 1 on thread: 12
Count = 0 on thread: 12
Count = 1 on thread: 12
Count = 0 on thread: 12
Count = 1 on thread: 12
AdjustCountSync exited on thread: 12
AdjustCountSync entered on thread: 11
Count = 0 on thread: 11
Count = 1 on thread: 11
Count = 0 on thread: 11
Count = 1 on thread: 11
Count = 0 on thread: 11
Count = 1 on thread: 11
AdjustCountSync exited on thread: 11
In the revised AdjustCountSync method we have synchronized the critical section
of code i.e. the loop in which the shared field is updated, with the Monitor class.
The CSharp lock and the Visual Basic SyncLock statements are expanded by the relevant
compiler to execute the Monitor.Enter and Monitor.Exit methods within a try, finally
block. When the first thread reaches the critical section, it acquires a lock and
then enters the section and can remain within the section, uninterrupted, until
it has finished its work. When the second thread reaches the critical section it
cannot acquire a lock and so it is queued until the first thread releases its lock,
when that happens the second thread can then acquire a lock and enter the critical
section. The output from the revised AdjustCountSync method clearly shows that only
one thread at a time is within the critical section of code and the method is working
as expected, _count is alternating between 0 and 1.
When using the Monitor class, whether explicitly or implicitly with the lock (SyncLock
in Visual Basic) statement, don't be tempted to use the this (Me in Visual Basic)
reference as the target of the lock or the class type for static data, as these
can both cause problems when used with public types. Declare a private class level
Object field as a lock target or a private static (Shared in Visual Basic) field
for static data. The Monitor class is very easy to use, especially with the lock
or SyncLock statements but there are several other techniques worth considering,
some of which are much more fine grained and/or more efficient than the Monitor.
We strongly recommend that you take some time to study the various classes in the
System.Threading namespace and we particularly recommend the Interlocked class,
as it provides very efficient methods for assigning or exchanging, incrementing
and decrementing variables in an atomic and therefore thread-safe manner.
A thread that is queued on a synchronization lock, waiting for an object to be signalled
or waiting for a method call to return is said to be blocking. A blocked thread
cannot continue executing until the relevant lock is released, the wait object is
signalled or the method call returns. Therefore multithreaded code that contains
a lot of synchronized code sections can, when thread scheduling is taken into account,
run more slowly than similar code running on a single thread. Designing and coding
a multithreaded application to produce the expected results, efficiently and reliably
can be challenging, get it right and you will have an application that really flies,
however, get it wrong and you will be in debugging hell trying to figure out why
the application works fine sometimes but occasionally either runs like a slug or
produces results that are garbage.
Another reason for the need to synchronize threads is the situation where two or
more threads are racing to a particular piece of code and the result of the operation
relies on which thread gets to the specific piece of code first. This is known as
a race condition and is either a design flaw or a bug, depending on which side of
the fence you sit! A fairly typical indication that a multithreaded application
is suffering from a race condition is when the application does not produce consistent results,
from known input when it is run for a long period of time and when the
result of a given run cannot be predicted. Faced with this situation, the threads
accessing the relevant section of code need to be synchronized, so that you can
be sure the threads always access the code in the correct order or better still
the problem needs to completely designed out i.e. the race condition has to be eliminated.
There is just one last topic for the locking and blocking, doom and gloom, section,
consider the situation where each of two threads tries to acquire a lock on a resource
that the other thread has already locked. This is known, unsurprisingly, as a deadlock,
in this situation neither of the two threads can make further progress. It is tempting
to assume that this type of situation is relatively easy to avoid but on large projects
with several or many developers the big picture can become obscured or lost entirely
and if it does, potential deadlocks can creep into the code and they won't always
show up in testing, however, you can bet that they will show up as soon as the system
is out of the door.
BackgroundWorker Component
The BackgroundWorker component is located in the System.ComponentModel namespace
and derives from the System.ComponentModel.Component class. If you are using Visual
Studio 2005 you can add a BackgroundWorker component to your project by dragging
it from the toolbox onto a form. The BackgroundWorker will appear in the component
tray and you can use the properties windows to set the relevant properties and hook
up the events. You can also use the BackgroundWorker component directly from code
by creating an instance of the class, setting the properties and hooking up the
relevant event handlers. Therefore the BackgroundWorker component is a good choice
for running a background task directly in a form, whether or not that is a good
design choice we will leave you to decide, however, there is nothing to stop you
from using the BackgroundWorker component in any class, as we will demonstrate later
in the article.
The BackgroundWorker component uses the System.ComponentModel.AsyncOperationManager
class to retrieve an AsyncOperation which it uses to manage the background operation.
The AsyncOperation class uses the System.Threading.SynchronizationContext class
to ensures that the event handlers are called on an appropriate thread without interrupting
the asynchronous operation i.e. the thread running the asynchronous operation does
not block whilst waiting for the event handlers to finish. This is a point worth
noting when you are designing your asynchronous operation and your event handlers,
you need to balance the interval at which you are raising a progress event with
the amount of work that the progress event handler is doing to avoid saturating
your application with event calls.
When using the BackgroundWorker in a (standard) Windows Forms application, the ProgressChanged
and RunWorkerCompleted event handlers are called on the main UI thread enabling
the direct updating of UI controls. However, beneath the surface the BackgroundWorker
class uses an asynchronous delegate to execute the background operation, therefore,
its DoWork event handler, which is used to run the background operation, is called
on a thread from the thread pool and cannot, therefore, be used to directly update
a UI control.
You can download the source code, using the link provided in the "Article Options" on
the left of the page, as part of either a CSharp
or a Visual Basic demonstration Visual Studio 2005 project. The source code includes
a demonstration of the BackgroundWorker component being used to run a background
task from within a form. We do not have the space to show the full listing here
but we will endeavour to show the important methods.
The code sample below shows how to run a background task using the BackgroundWorker
component, some helper methods have been omitted for clarity:
C# background task using the BackgroundWorker
component
private BackgroundWorker _worker;
private const string STATE_BACKGROUND_WORKER = "BackgroundWorker Task";
private const float DEMO_TASK_LENGTH = 60000F;
private const float PROGRESS_INTERVAL_DEFAULT = 5F;
private const string STATUS_FORMAT = "{0}: {1}% complete...";
private void BackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
LongTask(worker);
if (worker.CancellationPending)
{
e.Cancel = true;
}
}
private void BackgroundWorkerProgressChanged(object sender,
ProgressChangedEventArgs e)
{
if (e != null)
{
string state = e.UserState as string;
if (state == null)
{
state = "Unknown Caller";
}
labelWorkerProgress.Text = string.Format(STATUS_FORMAT, state,
e.ProgressPercentage);
progressBarWorker.Value = e.ProgressPercentage;
}
}
private void BackgroundWorkerRunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
string status;
if (e.Error != null)
{
status = string.Concat("Error: ", e.Error.Message);
}
else if (e.Cancelled)
{
status = "Task Cancelled";
}
else
{
status = "Task Complete";
}
BackgroundWorkerUpdateCompletionStatus(status);
}
private void LongTask(BackgroundWorker worker)
{
if (!worker.CancellationPending)
{
int sleepInterval = (int)(DEMO_TASK_LENGTH *
(PROGRESS_INTERVAL_DEFAULT / 100));
int progressComplete = 0;
while (progressComplete < DEMO_TASK_LENGTH)
{
if (worker.CancellationPending)
{
break;
}
// Simulate work...
if (sleepInterval > 0)
{
Thread.Sleep(sleepInterval);
}
progressComplete += sleepInterval;
// Raise progress changed...
if (worker.WorkerReportsProgress)
{
worker.ReportProgress((int)((
(float)progressComplete /
DEMO_TASK_LENGTH) * 100),
STATE_BACKGROUND_WORKER);
}
// Ensure we end on DEMO_TASK_LENGTH...
if ((progressComplete + sleepInterval) > DEMO_TASK_LENGTH)
{
// Adjust to finish...
sleepInterval = (int)(DEMO_TASK_LENGTH - progressComplete);
}
}
}
}
private void buttonWorkerCancel_Click(object sender, EventArgs e)
{
if (_worker != null)
{
_worker.CancelAsync();
}
}
private void buttonWorkerRun_Click(object sender, EventArgs e)
{
BackgroundWorkerResetProgress();
_worker = new BackgroundWorker();
_worker.WorkerReportsProgress = true;
_worker.WorkerSupportsCancellation = true;
_worker.DoWork += this.BackgroundWorkerDoWork;
_worker.ProgressChanged += this.BackgroundWorkerProgressChanged;
_worker.RunWorkerCompleted += this.BackgroundWorkerRunWorkerCompleted;
labelWorkerProgress.Text = string.Format(STATUS_FORMAT,
STATE_BACKGROUND_WORKER, 0);
_worker.RunWorkerAsync();
buttonWorkerRun.Enabled = false;
labelWorkerStatus.Text = "Running";
}
VB background task using the BackgroundWorker
component
Private _worker As BackgroundWorker
Private Const STATE_BACKGROUND_WORKER As String = "BackgroundWorker Task"
Private Const DEMO_TASK_LENGTH As Single = 60000.0F
Private Const PROGRESS_INTERVAL_DEFAULT As Single = 5.0F
Private Const STATUS_FORMAT As String = "{0}: {1}% complete..."
Private Sub BackgroundWorkerDoWork(ByVal sender As Object, _
ByVal e As DoWorkEventArgs)
Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
LongTask(worker)
If (worker.CancellationPending) Then
e.Cancel = True
End If
End Sub
Private Sub BackgroundWorkerProgressChanged(ByVal sender As Object, _
ByVal e As ProgressChangedEventArgs)
If (e IsNot Nothing) Then
Dim state As String
If (e.UserState IsNot Nothing) Then
state = CType(e.UserState, String)
Else
state = "Unknown Caller"
End If
labelWorkerProgress.Text = String.Format(STATUS_FORMAT, state, _
e.ProgressPercentage)
progressBarWorker.Value = e.ProgressPercentage
End If
End Sub
Private Sub BackgroundWorkerRunWorkerCompleted(ByVal sender As Object, _
ByVal e As RunWorkerCompletedEventArgs)
Dim status As String
If (e.Error IsNot Nothing) Then
status = String.Concat("Error: ", e.Error.Message)
ElseIf (e.Cancelled) Then
status = "Task Cancelled"
Else
status = "Task Complete"
End If
BackgroundWorkerUpdateCompletionStatus(status)
End Sub
Private Sub LongTask(ByVal worker As BackgroundWorker)
If Not (worker.CancellationPending) Then
Dim sleepInterval As Integer = CType((DEMO_TASK_LENGTH * _
(PROGRESS_INTERVAL_DEFAULT / 100)), Integer)
Dim progressComplete As Integer = 0
While (progressComplete < DEMO_TASK_LENGTH)
If (worker.CancellationPending) Then
Exit While
End If
' Simulate work...
If (sleepInterval > 0) Then
Thread.Sleep(sleepInterval)
End If
progressComplete += sleepInterval
' Raise progress changed...
If (worker.WorkerReportsProgress) Then
worker.ReportProgress(CType(((CType(progressComplete, Single) _
/ DEMO_TASK_LENGTH) * 100), Integer), _
STATE_BACKGROUND_WORKER)
End If
' Ensure we end on DEMO_TASK_LENGTH...
If ((progressComplete + sleepInterval) > DEMO_TASK_LENGTH) Then
' Adjust to finish...
sleepInterval = CType(DEMO_TASK_LENGTH - progressComplete, Integer)
End If
End While
End If
End Sub
Private Sub buttonWorkerCancel_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles buttonWorkerCancel.Click
If (_worker IsNot Nothing) Then
_worker.CancelAsync()
End If
End Sub
Private Sub buttonWorkerRun_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles buttonWorkerRun.Click
BackgroundWorkerResetProgress()
_worker = New BackgroundWorker()
_worker.WorkerReportsProgress = True
_worker.WorkerSupportsCancellation = True
AddHandler _worker.DoWork, AddressOf Me.BackgroundWorkerDoWork
AddHandler _worker.ProgressChanged, AddressOf Me.BackgroundWorkerProgressChanged
AddHandler _worker.RunWorkerCompleted, AddressOf _
Me.BackgroundWorkerRunWorkerCompleted
labelWorkerProgress.Text = String.Format(STATUS_FORMAT, _
STATE_BACKGROUND_WORKER, 0)
_worker.RunWorkerAsync()
buttonWorkerRun.Enabled = False
labelWorkerStatus.Text = "Running"
End Sub
The sample code is contained in a Form class and consists of two Button.Click events
for running and cancelling the background task, handlers for the BackgroundWorker
DoWork, ProgressChanged and RunWorkerCompleted events and the LongTask method that
simulates a background task. The form also contains a Label (labelWorkerStatus)
to display a status message, another Label (labelWorkerProgress) to display a progress
message and a ProgressBar (progressBarWorker) to display the progress of the background
task.
To start the background task, the run button click event handler, creates a new
instance of the BackgroundWorker class, sets its WorkerReportsProgress and WorkerSupportsCancellation
properties to True, so that we can raise the ProgressChanged event and if necessary
cancel the background task. The handler then sinks all three of the BackgroundWorker
events and calls the BackgroundWorker.RunWorkerAsync method to start the background
task. There is another overload of the RunWorkerAsync method that accepts an argument
Object, if you use this overload the DoWorkEventArgs that is passed to the DoWork
event handler will contain the specified argument Object when it is called by the
BackgroundWorker.
The DoWork event is handled by the BackgroundWorkerDoWork method, this method is
called on a background thread and therefore we cannot directly update any UI controls
from the code executed by this method. The sample just calls the nonsense LongTask
method to simulate a background task, the method just runs for one minute and raises
the ProgressChanged event at five percent intervals. There are, however, several
points worth noting in the LongTask method, which is why we have included it in
the listing. The method does not use the class level BackgroundWorker field reference
(_worker), instead it uses the instance reference passed to the handler, this decouples
the handler code from the form field and allows the background task to be used with
any BackgroundWorker instance.
The method checks the BackgroundWorker.CancellationPending property, before doing
anything and returns immediately if it is True. The method then enters a loop to
simulate work, within the loop, the CancellationPending property is checked again
on each iteration and the loop exits and therefore, the method returns, if at any
point the property returns True. At five percent intervals the BackgroundWorker.ReportProgress
method is called to raise the ProgressChanged event. Finally, when the LongTask
method returns to the BackgroundWorkerDoWork method, the CancellationPending property
is checked again and the DoWorkEventArgs.Cancel property is set to True if the background
task has been cancelled. This innocuous looking piece of code highlights
one of the potential problems of working with multiple, concurrent, threads, if
the CancellationPending property is set to True just as the LongTask
method is finishing, this piece of code will flag the task as cancelled even though, in reality, it has
finished. We don't think that this would be an issue in this case, as the original
caller would be expecting the task to be cancelled. However, if it was an issue,
we could, for example, change the LongTask method to return a Boolean indicating
whether or not it had been cancelled or we could pass the DoWorkEventArgs to the
LongTask method and remove the check from the BackgroundWorkerDoWork method.
The ProgressChanged event is handled by the BackgroundWorkerProgressChanged method,
this method is called on the main UI thread, therefore we can directly update the
UI controls to reflect the progress of the background task. The BackgroundWorker
component raises the ProgressChanged event, if its WorkerReportsProgress property
is set to True, whenever its ReportProgress method is called from the background
task.
The RunWorkerCompleted event is handled by the BackgroundWorkerRunWorkerCompleted
method, this method is called on the main UI thread, therefore we can directly update
the UI controls to reflect the outcome of the background task. The BackgroundWorker
component raises the RunWorkerCompleted event when the background task returns,
when the background task is cancelled or when the background task throws an exception.
Therefore, the handler code should check for all of these conditions to ascertain
the outcome of the background task.
To cancel the background task, the cancel button click event handler calls the BackgroundWorker.CancelAsync
method. The CancelAsync method will, if the WorkerSupportsCancellation property
is set to True, set the CancellationPending property to True. The background task
should periodically check the CancellationPending property and should stop execution
and return if the property is set to True. No matter how diligent the background
task is in checking the CancellationPending property it is still possible for it
to miss the property being set to True, occasionally, the background task will finish
its work and return without noticing that the task has been cancelled. In this situation,
the RunWorkerCompletedEventArgs passed to the RunWorkerCompleted event handler will
not the show the task as cancelled. Although not infallible, the technique of using
a cancel flag to prematurely stop a task running on a background thread is one of
the better options, aborting one thread from another can be unpredictable and therefore
care should be taken when, for example, using the Thread.Abort method.
Asynchronous Methods
When you call a method that is either in the same class or in another class, by
default, the call is synchronous, the code after the method call does not execute
until the called method finishes its work and returns. A call to an asynchronous
method however, always returns immediately, the called method starts a background
thread on which to perform its work and then returns to the caller, the caller can
continue to execute on its thread whilst the called method executes concurrently
(on multiprocessor or multi-core machines) on a background thread. Asynchronous
methods are, typically, implemented to run background or long running tasks. When
designing the implementation of background or long running tasks, classes or components
with asynchronous methods can be used to hide the potential complexity of multithreading
from calling code.
Asynchronous delegates are frequently used to execute methods on a background thread,
they are fairly straight forward to use and further abstract the potential complexity
of creating, managing and running threads. An asynchronous delegate i.e. a delegate
that implements BeginInvoke and EndInvoke methods, is executed, by the common language
runtime (CLR), on a thread from the thread pool. If a call-back method is specified,
when creating the delegate, it is called, when the asynchronous operation is complete,
on the thread pool thread. If the asynchronous task raises events, to report progress,
for example, the event handlers will, by default, be called on the thread pool thread.
Using an asynchronous delegate's BeginInvoke method you can call any method asynchronously,
as the CLR will retrieve a thread from the thread pool and use it to call the delegate's
target method. Before using this technique to call methods that have not been specifically
designed to run asynchronously, however, you should make sure that you have a good
understanding of what the target method does and how it does it, to ensure that
it is suitable for running on a background thread.
IAsyncResult Design Pattern
The IAsyncResult Design Pattern is defined as the implementation of two methods
BeginMethodName and EndMethodName that begin and end the asynchronous operation
MethodName. After calling BeginMethodName, the calling code can continue to execute
on the calling thread, whilst the asynchronous operation executes a different thread.
Each call to BeginMethodName must be matched with a call to EndMethodName to retrieve
the results of the operation. When the BeginMethodName method is called, the common
language runtime (CLR) queues the request and returns immediately. The MethodName
method is called as soon as possible on a thread from the thread pool, the original
calling method can continue to execute, on its own thread, in parallel with the
MethodName method which is executing on a thread pool thread.
If a call-back method has been specified in the call to the BeginMethodName method,
the call-back method will be called when MethodName returns. In the call-back method,
a call to the EndMethodName method must be made to end the asynchronous operation
and retrieve the return value and any out parameter values. If no call-back method
is specified when calling BeginMethodName, the EndMethodName method can be called
from the thread that called BeginMethodName. When not specifying a call-back method,
the caller can determine when MethodName has completed and therefore when to call
EndMethodName either by waiting on or polling the operation using the IAsyncResult
returned from the BeginMethodName method.
You can download the source code, using the link provided in the "Article Options" on
the left of the page, as part of either a CSharp
or a Visual Basic demonstration Visual Studio 2005 project. The source code includes
a demonstration of the IAsyncResult Design Pattern implemented in a class named
AsyncClass. We do not have the space to show the full listing here but we will endeavour
to show the important methods and the relevant calling code.
The code sample below shows how to run a background task using the IAsyncResult
Design Pattern. Some methods and properties have been omitted for clarity and brevity:
C# IAsyncResult Design Pattern
internal class AsyncClass
{
public event ProgressChangedEventHandler ProgressChanged;
// The delegate that is used to call the LongTask method on a
// thread from the thread pool.
private delegate void LongTaskCaller(object progressState);
private bool _cancelPending;
protected virtual void OnProgressChanged(ProgressChangedEventArgs e)
{
ProgressChangedEventHandler handler = ProgressChanged;
if (handler != null)
{
handler(this, e);
}
}
public IAsyncResult BeginLongTask(AsyncCallback callback, object progressState)
{
LongTaskCaller caller = new LongTaskCaller(this.LongTask);
return caller.BeginInvoke(progressState, callback, caller);
}
public void CancelAsync()
{
_cancelPending = true;
}
public void EndLongTask(IAsyncResult ar)
{
LongTaskCaller caller = ar.AsyncState as LongTaskCaller;
if (caller != null)
{
caller.EndInvoke(ar);
}
}
public void LongTask(object progressState)
{
// Method that can be called directly (synchronously) or
// asynchronously by calling BeginLongTask.
// Raises the ProgressChanged event by calling OnProgressChanged.
// Checks the CancelPending property to see if the task has been
// cancelled.
}
}
internal partial class Form1 : Form
{
// Delegates used to update the UI in a thread-safe manner from the
// asynchronous delegate code.
private delegate void ProgressUpdateCaller(ProgressChangedEventArgs e);
private delegate void CompletionStatusCaller(string status);
private AsyncClass _asyncClass;
private const string STATE_ASYNC_DELEGATE = "Asynchronous Delegate Task";
private void AsyncProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (this.InvokeRequired)
{
// Use Control.Invoke to marshal the data to the UI thread and then
// call the AsyncUpdateProgress method on the UI thread...
this.Invoke(new ProgressUpdateCaller(
this.AsyncUpdateProgress),
new object[] { e });
}
else
{
// Should never be reached...
AsyncUpdateProgress(e);
}
}
private void AsyncTaskCallback(IAsyncResult ar)
{
_asyncClass.EndLongTask(ar);
string status;
if (_asyncClass.Cancelled)
{
status = "Task Cancelled";
}
else
{
status = "Task Complete";
}
if (this.InvokeRequired)
{
this.Invoke(new CompletionStatusCaller(
this.AsyncUpdateCompletionStatus),
new object[] { status });
}
else
{
// Should never be reached...
AsyncUpdateCompletionStatus(status);
}
}
private void buttonAsyncCancel_Click(object sender, EventArgs e)
{
if (_asyncClass != null)
{
_asyncClass.CancelAsync();
}
}
private void buttonAsyncRun_Click(object sender, EventArgs e)
{
AsyncResetProgress();
_asyncClass = new AsyncClass();
_asyncClass.ProgressChanged += this.AsyncProgressChanged;
IAsyncResult result = _asyncClass.BeginLongTask(
this.AsyncTaskCallback, STATE_ASYNC_DELEGATE);
buttonAsyncRun.Enabled = false;
labelAsyncStatus.Text = "Running";
}
}
VB IAsyncResult Design Pattern
Friend Class AsyncClass
Public Event ProgressChanged As ProgressChangedEventHandler
' The delegate that is used to call the LongTask method on a
' thread from the thread pool.
Private Delegate Sub LongTaskCaller(ByVal progressState As Object)
Private _cancelPending As Boolean
Protected Overridable Sub OnProgressChanged(ByVal e As ProgressChangedEventArgs)
RaiseEvent ProgressChanged(Me, e)
End Sub
Public Function BeginLongTask(ByVal callback As AsyncCallback, _
ByVal progressState As Object) As IAsyncResult
Dim caller As LongTaskCaller = New LongTaskCaller(AddressOf Me.LongTask)
Return caller.BeginInvoke(progressState, callback, caller)
End Function
Public Sub CancelAsync()
_cancelPending = True
End Sub
Public Sub EndLongTask(ByVal ar As IAsyncResult)
Dim caller As LongTaskCaller = TryCast(ar.AsyncState, LongTaskCaller)
If (caller IsNot Nothing) Then
caller.EndInvoke(ar)
End If
End Sub
Public Sub LongTask(ByVal progressState As Object)
' Method that can be called directly (synchronously) or
' asynchronously by calling BeginLongTask.
' Raises the ProgressChanged event by calling OnProgressChanged.
' Checks the CancelPending property to see if the task has been
' cancelled.
End Sub
End Class
Friend Class Form1
' Delegates used to update the UI in a thread-safe manner from the
' asynchronous delegate code.
Private Delegate Sub ProgressUpdateCaller( _
ByVal e As ProgressChangedEventArgs)
Private Delegate Sub CompletionStatusCaller( _
ByVal status As String)
Private _asyncClass As AsyncClass
Private Const STATE_ASYNC_DELEGATE As String = _
"Asynchronous Delegate Task"
Private Sub AsyncProgressChanged(ByVal sender As Object, _
ByVal e As ProgressChangedEventArgs)
If (Me.InvokeRequired) Then
' Use Control.Invoke to marshal the data to the UI thread and then
' call the AsyncUpdateProgress method on the UI thread...
Me.Invoke(New ProgressUpdateCaller(AddressOf Me.AsyncUpdateProgress), _
New Object() {e})
Else
' Should never be reached...
AsyncUpdateProgress(e)
End If
End Sub
Private Sub AsyncTaskCallback(ByVal ar As IAsyncResult)
_asyncClass.EndLongTask(ar)
Dim status As String
If (_asyncClass.Cancelled) Then
status = "Task Cancelled"
Else
status = "Task Complete"
End If
If (Me.InvokeRequired) Then
Me.Invoke(New CompletionStatusCaller( _
AddressOf Me.AsyncUpdateCompletionStatus), _
New Object() {status})
Else
' Should never be reached...
AsyncUpdateCompletionStatus(status)
End If
End Sub
Private Sub buttonAsyncCancel_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles buttonAsyncCancel.Click
If (_asyncClass IsNot Nothing) Then
_asyncClass.CancelAsync()
End If
End Sub
Private Sub buttonAsyncRun_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles buttonAsyncRun.Click
AsyncResetProgress()
_asyncClass = New AsyncClass()
AddHandler _asyncClass.ProgressChanged, _
AddressOf Me.AsyncProgressChanged
Dim result As IAsyncResult = _asyncClass.BeginLongTask( _
AddressOf Me.AsyncTaskCallback, _
STATE_ASYNC_DELEGATE)
buttonAsyncRun.Enabled = False
labelAsyncStatus.Text = "Running"
End Sub
End Class
The sample code is contained in a demonstration class that implements BeginLongTask
and EndLongTask methods that execute the LongTask method asynchronously. The LongTask
method is virtually the same as the BackgroundWorker example, however, unlike the
BackgroundWorker example, it raises the ProgressChanged event directly on the background
thread. When using an asynchronous delegate directly, any event handlers that we
call and the call-back method are all called on the background thread. Forcing the
client or calling code to marshal the data in the event handlers is not a particularly
user friendly way of implementing an asynchronous operation in a class or component.
It could be argued that when, as in this example, the class or component is in the
same assembly as the calling code and therefore both are likely to be coded by the
same developer, that this is an acceptable solution. However, when developing a
class library class or component that is to be used in multiple projects we strongly
recommend that you consider implementing the Event Based Asynchronous Design Pattern
rather than the IAsyncResult Design Pattern. The Event Based Asynchronous Design
Pattern involves a bit more work on the class or component side but it makes the
asynchronous class or component much easier for client or calling code to use.
The client or calling code in the sample is contained in a Form class that has two
Button.Click event handlers, one to run the asynchronous method and one to, optionally,
cancel it. The run handler creates an instance of the demonstration AsyncClass,
sinks its ProgressChanged event and then calls the BeginLongTask method, passing
in an AsyncCallback delegate representing the method that is to be called when the
task has completed. The IAsyncResult that is returned by the BeginLongTask method
can be used to wait for the task to complete or to poll for the task to complete.
However, as both of these options can impact the performance of the UI, we have
opted to specify a call-back method, so that the UI will remain responsive whilst
the task is executing in the background. The AsyncProgressChanged method handles
the AsyncClass.ProgressChanged event and the AsyncTaskCallback method will be called
by the asynchronous delegate when the task has completed. Both of these methods
will be called on the background thread and both update UI controls, therefore,
both use the Control.Invoke method to perform UI control updates on the correct
thread. The AsyncTaskCallback method calls the EndLongTask method to end the asynchronous
task, each call to the BeginLongTask method must be matched by a call to the EndLongTask
method. Although we are not doing so in the sample, the EndMethodName implementation
can have ref or out parameters and/or a return value to retrieve the results of
an asynchronous task.
Event Based Asynchronous Design Pattern
The Event Based Asynchronous Design Pattern is defined as the implementation of
one or more methods named MethodNameAsync. These methods may mirror synchronous
methods that perform the same operation on the current thread. The class implementing
the MethodNameAsync method may have a MethodNameCompleted event and a MethodNameCancelAsync
method. The implementation of the event based asynchronous design pattern may take
several forms, the simplest class may have a single MethodNameAsync method and a
corresponding MethodNameCompleted event. More complex implementations may have several
MethodNameAsync methods, each with a corresponding MethodNameCompleted event. Classes
may also, optionally, support the cancellation of an asynchronous operation, progress
reporting and incremental results for each asynchronous operation. A more advanced
implementation may support multiple pending calls (multiple invocation) allowing
an asynchronous method to be called many times before it completes other pending
operations.
AsyncComponent Base Class
To demonstrate an implementation of the Event Based Asynchronous Design Pattern
we have developed the AsyncComponent class. The AsyncComponent class extends the
System.ComponentModel.Component class to provide a component base class with built
in support for executing asynchronous methods. We can't show all of the code in
the AsyncComponent class in this article but we will outline the specification,
describe how to use AsyncComponent and describe the functionality of the main, protected
and public, methods.
You can download the source code, using the link provided in the "Article Options" on the left of
the page, for the AsyncComponent class and its associated
event argument data classes as part of either a CSharp or a Visual Basic demonstration
Visual Studio 2005 project. The source code is intended, primarily, to demonstrate
an implementation of the Event Based Asynchronous Design Pattern, however, you can
make use of the code either in full or in part in your own applications without
charge (see the copyright and disclaimer notice in the AsyncComponent class for
full details).
The AsyncComponent base class can be used, by a derived class, to execute multiple
methods asynchronously, however, only one asynchronous method or task can be running
at a time. The AsyncComponent class provides functionality that allows a derived
class to pass user state, as part of the event data, to both the ProgressChanged
event and the TaskCompleted event, therefore the original caller can determine which
asynchronous method is reporting progress or has just completed. You should note,
however, that user state, as implemented in the AsyncComponent class is intended
to allow the original caller to identify a single invocation of a particular method
rather than to identify a particular invocation of a multiple invocation method.
The difference may, at first glance, seem subtle but it is important to realise
that the AsyncComponent class does not allow a specific method to be executed multiple
times concurrently, in addition the AsyncComponent class does not allow multiple,
different, methods to execute concurrently.
The AsyncComponent class contains the BackgroundWorker class and surfaces some of
its properties, methods and events, some in renamed form, publicly to external callers
and the Visual Studio 2005 properties window. Protected methods are provided to
allow a derived class to start, manage and execute an asynchronous task using the
contained BackgroundWorker.
The BackgroundWorker class, contained by the AsyncComponent class, does not override
the protected Component.Dispose method of its base class and therefore, as it does
not need to call Dispose on its contained BackgroundWorker class, there is no need
for the AsyncComponent class to override the protected Dispose method of its Component
base class. However, an AsyncComponent derived class should override the Component.Dispose
method if it has resources that require disposing.
The AsyncComponent class has the following public properties:
- CancellationPending
-
A virtual (
Overridable in Visual Basic) read only property that delegates to the
BackgroundWorker.CancellationPending property. If there is no BackgroundWorker reference
when the property is called it returns False.
- IsBusy
-
A virtual (
Overridable in Visual Basic) read only property that delegates to the
BackgroundWorker.IsBusy property. If there is no BackgroundWorker reference when
the property is called it returns False.
- ReportsProgress
-
Gets or sets whether the
AsyncComponent reports progress i.e. whether or not it
raises the ProgressChanged event. The default value is False.
- SupportsCancellation
-
Gets or sets whether the
AsyncComponent allows an asynchronous task to be cancelled.
The default value is False.
The AsyncComponent class has the following protected methods:
- virtual void OnProgressChanged(ProgressChangedEventArgs e)
-
A virtual (
Overridable in Visual Basic) method that raises the ProgressChanged event.
This method will always be called on a thread that is appropriate for the original
caller, typically, that will be the main UI thread.
- void ReportProgress(Object taskId, Int32 percentProgress)
void ReportProgress(Object taskId, Int32 percentProgress, Object userState)
-
Derived classes should call this method, from within their asynchronous task method,
to raise the
ProgressChanged event on a thread that is appropriate for the original
caller. Ultimately a call to this method results in an asynchronous internal call
to the OnProgressChanged method on the relevant thread. The internal call to the
OnProgressChanged method is only made if the AsyncComponent.ReportsProgress property
is set to True.
- virtual void RunTask(Object taskId, RunTaskArguments arguments)
-
A virtual (
Overridable in Visual Basic) method that is called on a background thread.
Derived classes should override this method and run their asynchronous method from
it. The taskId parameter is the same value that was passed to the RunTaskAsync method
and should be used by a derived class to identify the method to execute. The arguments
parameter contains the data needed to execute the asynchronous task, such as, the
argument object that was passed to the RunTaskAsync method and properties that a
derived class can set to specify if the task was cancelled, the result of the task
and the state object passed in by the original caller. A derived class can call
the ReportProgress method periodically during the execution of the asynchronous
task, without blocking the task, to raise the ProgressChanged event. The task should
also monitor the CancellationPending property to check if the task has been cancelled
by the original caller. When this method returns, the AsyncComponent class will
raise the TaskCompleted event.
- void RunTaskAsync(Object taskId)
void RunTaskAsync(Object taskId, Object argument)
-
A derived class should call this method to start an asynchronous task. The
taskId
parameter can be used to uniquely identify the task that is to be executed. The
argument object will be passed, together with the taskId, in the RunTaskArguments
data that is passed to the RunTask method. Ultimately a call to this method will
result in an asynchronous internal call to the RunTask method on a background thread.
- Boolean TaskExists()
-
A utility method that a derived class can use to check if a background task currently
exists. Returns
True if a background task exists, whether it is running or not,
False if no task exists.
The AsyncComponent class has the following public methods:
- virtual void CancelAsync()
-
A virtual (
Overridable in Visual Basic) method that can be used by the original
caller of an asynchronous method in a derived class to cancel the task. Will set
the CancellationPending property to True, if the AsyncComponent.SupportsCancellation
property is True at the time of the call.
The AsyncComponent class has the following public events:
- ProgressChanged
-
The
ProgessChanged event is raised, by a derived class, by calling the AsyncComponent.ReportProgress
method. The event handler is passed a System.ComponentModel.ProgressChangedEventArgs
instance containing the event data and will be called on a thread appropriate to
the client, typically this will be the main UI thread. The ProgressChanged event
is only raised if the AsyncComponent.ReportsProgress property is set to True.
- TaskCompleted
-
The
TaskCompleted event is raised by the AsyncComponent when the RunTask method
returns. The event is raised when the asynchronous task completes, is cancelled
or throws an exception. The event handler is passed a TaskCompletedEventArgs instance
containing the event data and will be called on a thread appropriate to the client,
typically this will be the main UI thread.
As stated earlier in the article, the sample source code Visual Studio 2005 project
contains the AsyncComponent class and its associated event data classes. The project
also contains a class named CustomComponent, that derives from AsyncComponent, to
demonstrate how to use the AsyncComponent class.
If you want to use AsyncComponent in one of your own projects proceed as follows:
- Add the following classes to the project:
AsyncComponent, RunTaskArguments and
TaskCompletedEventArgs.
- From the Visual Studio 2005 Project menu, select New Item.
- In the New Item dialog, select the Component Class template and name the new
class.
- When the new class has been added to the project, change the base class from
Component to AsyncComponent. Note: If you
are using Visual Basic the base class declaration will be in the designer file. To open
the designer file, select the project in the solution explorer and click the Show All Files
(solution explorer toolbar) button. If you expand your new class file you will see the
<className>.Designer.vb file. Select the designer file and click the View Code
(solution explorer toolbar) button.
- In the new component class, create the MethodName method that is to be the background
task, the method can be private, protected or public. Don't forget to check the
base class
CancellationPending property periodically in your code. You can, optionally,
call the base class ReportProgress method to raise the ProgressChanged event. You
can have more than one background task method but only one can execute at a time.
- Create the MethodNameAsync method that is to be called by client code to run
the task asynchronously. In the method, check that an asynchronous task is not already
running by calling the base class
IsBusy property and then, if IsBusy
is False, call the base class RunTaskAsync
method, this is the method that starts the asynchronous task, specifying a unique
task ID for the MethodName method.
- Override the base class
RunTask method to run your asynchronous method on a background
thread.
Conclusion
The ability to run multiple tasks in parallel has been a desirable feature for server
components for a long time and the arrival of the .Net Framework, with its multi-language
support for a common framework, has made multithreading easily available to a greater
number of developers. We are now, increasingly, seeing multi-core processors and
to a lesser extent multiprocessor machines being used as everyday workstations,
therefore, multithreading in desktop applications and services is also becoming a
desirable and worthwhile feature. If you are developing a Windows Forms application
with fairly modest multithreading requirements then the BackgroundWorker component
will get you up and running very quickly using a programming technique that you
are already familiar with, namely, events and event handlers. If you are developing
a class or component library that is to be used by Windows Forms developers you
will have a bit more work to do, however, the .Net Frameworks contains all of the
classes that you will need and they are fairly easy to use.
Copyright ©Blayd Software Limited 2007-2010. All rights reserved.