![]() |
General Programming »
Threads, Processes & IPC »
Multi-threading
Beginner
License: The Code Project Open License (CPOL)
Beginners Guide To Threading In .NET Part 2 of nBy Sacha BarberBeginners Guide To Threading In .NET |
C# (C# 2.0, C# 3.0), .NET (.NET 2.0, .NET 3.0, .NET 3.5), WPF, Architect, Dev, Design, SysAdmin
|
||||||||||
|
Advanced Search |
|
|
|
||||||||||||||||
Im afraid to say that I am just one of those people that unless I am doing something, I am bored. So now that I finally feel I have learnt tha basics of WPF, it is time to turn my attention to other matters.
I have a long list of things that demand my attention such as (WCF/WF/CLR Via C# version 2 book), but I recently went for (and got, but turned it down in the end) which required me to know a lot about threading. Whilst I consider myself to be pretty good with threading, I thought yeah I'm ok at threading but I could always be better. So as a result of that I have decided to dedicate myself to writing a series of articles on threading in .NET. This series will undoubtely owe much to an excellent Visual Basic .NET Threading Handbook that I bought that is nicely filling the MSDN gaps for me and now you.
I suspect this topic will range from simple to medium to advanced, and it will cover a lot of stuff that will be in MSDN, but I hope to give it my own spin also.
I dont know the exact schedule, but it may end up being something like
I guess the best way is to just crack on. One note though before we start, I will be using C# and Visual Studio 2008.
What I'm going to attempt to cover in this article will be
The following figure illustrates the most common thread states, and what happens to move a thread into each state

Here a list of all the available thread states
| State | Description |
| Running | The thread has been started, it is not blocked, and there
is no pending ThreadAbortException. |
| StopRequested | The thread is being requested to stop. This is for internal use only. |
| SuspendRequested | The thread is being requested to suspend. |
| Background | The thread is being executed as a background thread, as opposed
to a foreground thread. This state is controlled by setting the Thread.IsBackground
property. |
| Unstarted | The Thread.Start method has not been invoked
on the thread. |
| Stopped | The thread has stopped. |
| WaitSleepJoin | The thread is blocked. This could be the result of calling
(we will be covering all of these locking /synchronization techniques in part 3) |
| Suspended | The thread has been suspended. |
| AbortRequested | The Thread.Abort method has been invoked on the
thread, but the thread has not yet received the pending System.Threading.ThreadAbortException
that will attempt to terminate it. |
| Aborted | The thread state includes AbortRequested and the thread is now dead, but its state has not yet changed to Stopped. |
Taken from the MSDN ThreadState page
In this section I will include some code that examines some of threading areas mentioned above. I will not cover all of them, but I'll try and cover most of them.
The Join method (without any parameters) blocks the calling thread until the current thread
is terminated. It should be noted that the caller will block indefinitely if
the current thread does not terminate. If the thread has already terminated
when Join method is called, the method returns immediately.
The Join method has an override, which lets you set the number of
milliseconds to wait on the thread to finish. If thread has not
finished when the timer expires, Join exits and returns control to the
calling thread (and the joined thread continues to execute).
This method changes the state of the calling thread to include WaitSleepJoin
(according to the MSDN documentation)
This method is quite useful if one thread depends on another thread.
Let see a small example (attached demo ThreadJoin project)
In this small example we have 2 threads, I want the 1st thread to run first
and the second thread to run after the 1st thread is completed.
using System;
using System.Threading;
namespace ThreadJoin
{
class Program
{
public static Thread T1;
public static Thread T2;
public static void Main(string[] args)
{
T1 = new Thread(new ThreadStart(First));
T2 = new Thread(new ThreadStart(Second));
T1.Name = "T1";
T2.Name = "T2";
T1.Start();
T2.Start();
Console.ReadLine();
}
//thread T1 threadStart
private static void First()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine(
"T1 state [{0}], T1 showing {1}",
T1.ThreadState, i.ToString());
}
}
//thread T2 threadStart
private static void Second()
{
//what the state of both threads
Console.WriteLine(
"T2 state [{0}] just about to Join, T1 state [{1}], CurrentThreadName={2}",
T2.ThreadState, T1.ThreadState,
Thread.CurrentThread.Name);
//join T1
T1.Join();
Console.WriteLine(
"T2 state [{0}] T2 just joined T1, T1 state [{1}], CurrentThreadName={2}",
T2.ThreadState, T1.ThreadState,
Thread.CurrentThread.Name);
for (int i = 5; i < 10; i++)
{
Console.WriteLine(
"T2 state [{0}], T1 state [{1}], CurrentThreadName={2} showing {3}",
T2.ThreadState, T1.ThreadState,
Thread.CurrentThread.Name, i.ToString());
}
Console.WriteLine(
"T2 state [{0}], T1 state [{1}], CurrentThreadName={2}",
T2.ThreadState, T1.ThreadState,
Thread.CurrentThread.Name);
}
}
}
And here is the output from this small program, where we can indeed see that thread T1 completes and then the thread T2 operations run.

Note : Thread T1 continues to run and then stops and then the operations that thread T2 specified are run.
Sleep
The static Thread.Sleep method available on the Thread
class is fairly simple, it simply Suspends the current thread for a specified
time. Consider the following example, where 2 threads are started that run 2
seperate counter methods, the 1st thread (T1) counts from 0-50 and the 2nd thread
(T2) counts from 51-100.
Thread T1 will go to sleep for 1 second when it reaches 10, and thread T2 will go to sleep for 5 seconds when it reaches 70.
Let see a small example (attached demo ThreadSleep project)
using System;
using System.Threading;
namespace ThreadSleep
{
class Program
{
public static Thread T1;
public static Thread T2;
public static void Main(string[] args)
{
Console.WriteLine("Enter Main method");
T1 = new Thread(new ThreadStart(Count1));
T2 = new Thread(new ThreadStart(Count2));
T1.Start();
T2.Start();
Console.WriteLine("Exit Main method");
Console.ReadLine();
}
//thread T1 threadStart
private static void Count1()
{
Console.WriteLine("Enter T1 counter");
for (int i = 0; i < 50; i++)
{
Console.Write(i + " ");
if (i == 10)
Thread.Sleep(1000);
}
Console.WriteLine("Exit T1 counter");
}
//thread T2 threadStart
private static void Count2()
{
Console.WriteLine("Enter T2 counter");
for (int i = 51; i < 100; i++)
{
Console.Write(i + " ");
if (i == 70)
Thread.Sleep(5000);
}
Console.WriteLine("Exit T2 counter");
}
}
}
The output may be as follows

In this example, thread T1 is run 1st, so starts its counter (as we will see
later, T1 may not necessarily be the thread to start 1st) and counts up to 10,
at which point T1 sleeps for 1 second and is placed in the WaitSleepJoin
state. At this point T2 runs so starts its counter, gets to 70 and is put to
sleep (and is placed in the WaitSleepJoin state) at which point
T1 is awoken and run to completion. T2 is then awoken and is able to complete
(as T1 has completed, so there is only T2 work left to do).
Interrupt
When a thread is put to sleep, the thread goes into the WaitSleepJoin
state. If the thread is in this state it may be placed back in the scheduling
queue by the use of the Interrupt method. Calling Interrupt when
a thread is in the WaitSleepJoin state will cause a ThreadInterruptedException
to be thrown, so any code that is written needs to catch this.
If this thread is not currently blocked in a wait, sleep, or join state, it
will be interrupted when it next begins to block.
Let see a small example (attached demo ThreadInterrupt project)
using System;
using System.Threading;
namespace ThreadInterrupt
{
class Program
{
public static Thread sleeper;
public static Thread waker;
public static void Main(string[] args)
{
Console.WriteLine("Enter Main method");
sleeper = new Thread(new ThreadStart(PutThreadToSleep));
waker = new Thread(new ThreadStart(WakeThread));
sleeper.Start();
waker.Start();
Console.WriteLine("Exiting Main method");
Console.ReadLine();
}
//thread sleeper threadStart
private static void PutThreadToSleep()
{
for (int i = 0; i < 50; i++)
{
Console.Write(i + " ");
if (i == 10 || i == 20 || i == 30)
{
try
{
Console.WriteLine("Sleep, Going to sleep at {0}",
i.ToString());
Thread.Sleep(20);
}
catch (ThreadInterruptedException e)
{
Console.WriteLine("Forcibly ");
}
Console.WriteLine("woken");
}
}
}
//thread waker threadStart
private static void WakeThread()
{
for (int i = 51; i < 100; i++)
{
Console.Write(i + " ");
if (sleeper.ThreadState == ThreadState.WaitSleepJoin)
{
Console.WriteLine("Interrupting sleeper");
sleeper.Interrupt();
}
}
}
}
}
Which may produce this output

It can be seen from this output that the sleeper thread starts normally, and
when it gets to 10, is put to sleep so goes into the WaitSleepJoin
state. Then the waker thread starts and immediately tries to Interrupt
the sleeper (which is in the WaitSleepJoin state, so the ThreadInterruptedException
is thrown and caught). However as the initial sleeper threads sleep time elapses,
it is again allowed to run until completion.
I personally haven't really had to use the Interrupt method that
often, but I do consider interrupting threads to be fairly dangerous, as you
just cant gaurentee where a thread is.
"Interrupting a thread arbitrarily is dangerous, however, because any framework or third-party methods in the calling stack could unexpectedly receive the interrupt rather than your intended code. All it would take is for the thread to block briefly on a simple lock or synchronization resource, and any pending interruption would kick in. If the method wasn't designed to be interrupted (with appropriate cleanup code in finally blocks) objects could be left in an unusable state, or resources incompletely released.
Interrupting a thread is safe when you know exactly where the thread is."
Threading in C#, Joseph Albahari.
Pause
There used to be a way to pause threads using the Pause() method. But this is now deprecated, so you must use alternative methods, such as WaitHandles. To demonstrate this there is a combined application that covers Pause/Resume and Abort of background threads
Resume
There used to be a way to pause threads using the Resume() method. But this is now deprecated, so you must use alternative methods, such as WaitHandles. To demonstrate this there is a combined application that covers Pause/Resume and Abort of background threads
Abort
First let me state that there is an Abort() method, but this is not something you should use likely (an in my own opinion at all). I would just like to first quote 2 reputable sources on the dangers of using the Abort() method
"A blocked thread can also be forcibly released via its Abort method. This has an effect similar to calling Interrupt, except that a ThreadAbortException is thrown instead of a ThreadInterruptedException. Furthermore, the exception will be re-thrown at the end of the catch block (in an attempt to terminate the thread for good) unless Thread.ResetAbort is called within the catch block. In the interim, the thread has a ThreadState of AbortRequested.
The big difference, though, between Interrupt and Abort, is what happens when it's called on a thread that is not blocked. While Interrupt waits until the thread next blocks before doing anything, Abort throws an exception on the thread right where it's executing – maybe not even in your code. Aborting a non-blocked thread can have significant consequences"
Threading in C#, Joseph Albahari.
"
A common question that emerges once you have kicked off some concurrent work is: how do I stop it? Here are two popular reasons for wanting to stop some work in progress:
You need to shut down the program.
The user cancelled the operation.
In the first case, it is often acceptable to drop everything mid flow and not bother shutting down cleanly, because the internal state of the program no longer matters, and the OS will release many resources held by our program when it exits. The only concern is if the program stores state persistently - it is important to make sure that any such state is consistent when our program exits. However, if we are relying on a database for such state, we can still often get away with abandoning things mid flow, particularly if we are using transactions - aborting a transaction rolls everything back to where it was before the transaction started, so this should be sufficient to return the system to a consistent state.
There are of course cases where dropping everything on the floor will not work. If the application stores its state on disk without the aid of a database, it will need to take steps to make sure that the on-disk representation is consistent before abandoning an operation. And in some cases, a program may have interactions in progress with external systems or services that require explicit cleanup beyond what will happen automatically. However, if you have designed your system to be robust in the face of a sudden failure (e.g. loss of power) then it should be acceptable simply to abandon work in progress rather than cleaning up neatly when shutting the program down. (Indeed there is a school of thought that says that if your program requires explicit shutdown, it is not sufficiently robust - for a truly robust program, sudden termination should always be a safe way to shut down. And given that, some say, you may as well make this your normal mode of shutdown - it's a very quick way of shutting down!)
User-initiated cancellation of a single operation is an entirely different matter however.
If the user chooses to cancel an operation for some reason - maybe it is taking too long - she will expect to be able to continue using the program afterwards. It is therefore not acceptable simply to drop everything on the floor, because the OS is not about to tidy up after us. Our program has to live with its internal state after the operation has been cancelled. It is therefore necessary for cancellation to be done in an orderly fashion, so that the program's state is still internally consistent once the operation is complete.
Bearing this in mind, consider the use of Thread.Abort. This is, unfortunately, a popular choice for cancelling work, because it usually manages to stop the target thread no matter what it was up to. This means you will often see its use recommended on mailing lists and news groups as a way of stopping work in progress, but it is really only appropriate if you are in the process of shutting down the program, because it makes it very hard to be sure what state the program will be in afterwards.
"
How To Stop a Thread in .NET (and Why Thread.Abort is Evil), Ian Griffiths.
So with all this in mind, I have created a small application which I believe to be a well behaved worker thread, that allows the user to carry out some background work, and Pause/Resume and Cancel it, all safely and easily. Its not the only way to do this, but it's a way.
Let see a small example (attached demo ThreadResumePause_StopUsingEventArgs project)
Unfortunately I had to include some UI code here, to allow the user to click on different buttons for Pause/Resume etc etc, but I shall only include the parts of the UI code that I feel are relevant to explaining the subject
So first here is the worker thread class, one important thing to note is the usage of the volatile keyword.
The volatile keyword indicates that a field can be modified in the program by something such as the operating system, the hardware, or a concurrently executing thread.
The system always reads the current value of a volatile object at the point it is requested, even if the previous instruction asked for a value from the same object. Also, the value of the object is written immediately on assignment.
The volatile modifier is usually used for a field that is accessed by multiple threads without using the lock statement to serialize access. Using the volatile modifier ensures that one thread retrieves the most up-to-date value written by another thread.
using System;
using System.ComponentModel;
using System.Threading;
namespace ThreadResumePause_StopUsingEventArgs
{
public delegate void ReportWorkDoneEventhandler(object sender,
WorkDoneCancelEventArgs e);
/// <summary>
/// This class provides a background worker that finds prime numbers, that
/// are reported to the UI via the ReportWorkDone event. The UI may pause
/// the worker by calling the Pause() method, and may resume the worker by
/// calling the Resume() method. The UI may also cancel the worker by setting
/// the ReportWorkDone events event args Cancel property to true.
/// </summary>
public class WorkerThread
{
private Thread worker;
public event ReportWorkDoneEventhandler ReportWorkDone;
private volatile bool cancel = false;
private ManualResetEvent trigger = new ManualResetEvent(true);
//ctor
public WorkerThread()
{
}
//Do the work, start the thread
public void Start(long primeNumberLoopToFind)
{
worker = new Thread(new ParameterizedThreadStart(DoWork));
worker.Start(primeNumberLoopToFind);
}
//Thread start method
private void DoWork(object data)
{
long primeNumberLoopToFind = (long)data;
int divisorsFound = 0;
int startDivisor = 1;
for (int i = 0; i < primeNumberLoopToFind; i++)
{
//wait for trigger
trigger.WaitOne();
divisorsFound = 0;
startDivisor = 1;
//check for prime numbers, and if we find one raise
//the ReportWorkDone event
while (startDivisor <= i)
{
if (i % startDivisor == 0)
divisorsFound++;
startDivisor++;
}
if (divisorsFound == 2)
{
WorkDoneCancelEventArgs e =
new WorkDoneCancelEventArgs(i);
OnReportWorkDone(e);
cancel = e.Cancel;
//check whether thread should carry on,
//perhaps user cancelled it
if (cancel)
return;
}
}
}
/// <summary>
/// make the worker thread wait on the ManualResetEvent
/// </summary>
public void Pause()
{
trigger.Reset();
}
/// <summary>
/// signal the worker thread, raise signal on
/// the ManualResetEvent
/// </summary>
public void Resume()
{
trigger.Set();
}
/// <summary>
/// Raise the ReportWorkDone event
/// </summary>
protected virtual void OnReportWorkDone(WorkDoneCancelEventArgs e)
{
if (ReportWorkDone != null)
{
ReportWorkDone(this, e);
}
}
}
//Simple cancellable EventArgs, that also exposes
//current prime number found to UI
public class WorkDoneCancelEventArgs : CancelEventArgs
{
public int PrimeFound { get; private set; }
public WorkDoneCancelEventArgs(int primeFound)
{
this.PrimeFound = primeFound;
}
}
}
And here is the relevant parts of the UI code (Winforms C#). Note that I have not checked whether an Invoke is actually required before doing an Invoke.
MSDN says the following about the Control.InvokeRequired Property :
Gets a value indicating whether the caller must call an invoke method when making method calls to the control because the caller is on a different thread than the one the control was created on.
Controls in Windows Forms are bound to a specific thread and are not thread safe. Therefore, if you are calling a control's method from a different thread, you must use one of the control's Invoke methods to marshal the call to the proper thread. This property can be used to determine if you must call an invoke method, which can be useful if you do not know what thread owns a control.
So one could use this to determine if an Invoke is actually required. Calling InvokeRequired/Invoke/BeginInvoke/EndInvoke are all thread safe
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace ThreadResumePause_StopUsingEventArgs
{
public partial class Form1 : Form
{
private WorkerThread wt = new WorkerThread();
private SynchronizationContext context;
private bool primeThreadCancel = false;
public Form1()
{
InitializeComponent();
//obtain the current SynchronizationContext
context = SynchronizationContext.Current;
}
void wt_ReportWorkDone(object sender, WorkDoneCancelEventArgs e)
{
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
//NOTE : This would also work to marshal call to UI thread
//+++++++++++++++++++++++++++++++++++++++++++++++++++++
//this.Invoke(new EventHandler(delegate
//{
// lstItems.Items.Add(e.PrimeFound.ToString());
//}));
//marshal call to UI thread
context.Post(new SendOrPostCallback(delegate(object state)
{
this.lstItems.Items.Add(e.PrimeFound.ToString());
}), null);
//should worker thread be caneclled, has user clicked cancel button?
e.Cancel = primeThreadCancel;
}
private void btnStart_Click(object sender, EventArgs e)
{
//start the worker and listen to its ReportWorkDone event
wt.Start(100000);
wt.ReportWorkDone +=
new ReportWorkDoneEventhandler(wt_ReportWorkDone);
primeThreadCancel= false;
}
private void btnCancel_Click(object sender, EventArgs e)
{
primeThreadCancel= true;
}
private void btnPause_Click(object sender, EventArgs e)
{
wt.Pause();
}
private void btnResume_Click(object sender, EventArgs e)
{
wt.Resume();
}
}
}
And when run this looks like this
So how does all this work. There are a few things in here that I has hoping not to get on to until part4, but there is simply no way they could be avoided so Ill try and cover them just enough. There are a couple of key concepts here, such as
I will try and explain each of these parts in turn
This is easily acheived by the use of a ParameterizedThreadStart where you simply start the thread, passing in an input parameter like worker.Start(primeNumberLoopToFind) and
then in the actual private void DoWork(object data) method you can get the parameter value by using the data parameter, like long primeNumberLoopToFind = (long)data
The worker thread raises the ReportWorkDone event which is used by the UI, but when the UI attempts to use this ReportWorkDone EventArg object properties to add items to the UI owned ListBox control, you will get a cross thread violation, unless you do something to marshal the thread to the UI thread. This is know as Thread affinity, the thread that creates the UI controls owns the contols, so any calls to the UI controls must go through the UI thread.
There are several ways of doing this, I am using the .NET 2.0 version of this which makes use of a class called SynchronizationContext whicn I obtain with the Forms constructor. Then I am free to marshall the
worker threads results to the UI thread so that that may be added to the UIs controls. This is done as follows
context.Post(new SendOrPostCallback(delegate(object state)
{
this.lstItems.Items.Add(e.PrimeFound.ToString());
}), null);
To pause the worker threac I make use of a Threading object called a ManualResetEvent which may be used to cause a thread to both wait and resume its operation, depending on the signal state
of the ManualResetEvent. Basically in a signalled state, the thread that is waiting on the ManualResetEvent will be allowed to continue, and in a non signalled state
the thread that is waiting on the ManualResetEvent will be forced to wait. If we examine the relavent parts of the WorkerThread clas
We declare a new ManualResetEvent which starts in signalled state
private ManualResetEvent trigger = new ManualResetEvent(true);
We then attempt to wait for the signalled state in the workerThread DoWork method. As the ManualResetEvent started out in the signalled state
the thread proceeds to run
for (int i = 0; i< primeNumberLoopToFind; i++)
{
//wait for trigger
trigger.WaitOne();
....
....
So for the pause all we need to do, is put the ManualResetEvent in the non signalled state (using the Reset method) which causes
the worker to wait for the ManualResetEvent to be put into a signalled state again
trigger.Reset();
The resume is easy, all we need to do, is put the ManualResetEvent in the signalled state (using the Set method) which causes
the worker to longer wait for the ManualResetEvent, as it is in a signalled state again
trigger.Set();
If you read Ian Griffiths article that I quoted above, you'll know that he simply suggests keep things as simple as possible, bu the use of a boolean
flag that is visible to both the UI and the worker thread. I have also done this but I use a CancelEventArgs. Which allows the user to a cancel state for the worker thread directly into the
CancelEventArgs, such that the worker thread can use this to see if it should be cancelled. Its works like this
CancelEventArgsCancelEventArgs cancel is setCancelEventArgs cancel is set, so breaks out of its workI just feel this is a little safer than using the Abort() method
There are some very obvious threading opportunities, which are as follows:
If a task can successfully be run in the background, then it is a candidate for threading. For example think of a search that needs to search 1000nds of items for matching items, this would be an excellent choice for a background thread to do.
Another example may be when you are using an external resource such as a database/web service/ remote file system, where there may be a performance penalty to pay for accessing these resources. By threading access to these sorts of things, you are alleviating some of the overhead incurred by accessing these resources within a single thread.
We can imagine that we have a User Interface (UI) that allows the user to do various tasks. Some of these tasks may taks quite a long time to complete. To put it in a real world context, let us say that the app is a email client application that allows users to create / fetch emails. Fetching emails may take a while to complete, as the fetching of emails must interact with a mail server to obtain the current users emails. Threading the fetch emails code would help to keep the UI resposive to further user interactions. If we dont thread tasks that take a long time in UIs and simply rely on the main thread, we could easily end up in a situation where the UI is fairly unresponsive. So this is a prime candidate for threading. As will see in a subsequent article, there is the issue of Thread Affinity that needs to be considered when dealing with UIs, but I'll save that discussion for the subsequent article.
If you have ever done any Socket programming you may have had to create a Server that was able to accept Clients. A typical arrangement of this may be a chat application where the Server is able to accept n-many clients and is able to read from Clients and write to Clients. This is largely achieved by Threads. Though I am aware that there is an asycnronous socket API available within .NET, so you may choose to use that instead of manually created threads. Sockets are still a valid threading example.
The best example of this that I have seen is located here at this link. The basic idea when working with sockets is that you have a Server and n-many Clients. The server is run (main thread is active) and then for each client connection request that is made, a new thread is created to deal with the Client. At the Client end it is typical that a Client should be able to receive messages from another Client (via the server) and the Client should also allow the Client user to type messages.
Let us just think about the Client for a minute, the Client is able to send messages to other Clients (via the Server), so that implies that there is a thread that needs to be able to respond to data that the user enters. The Client should also be able to show messages from other Clients (via the Server), so this also implies that this also needs to be on a Thread. If we use the same Thread to listen to incoming messages from other Clients, we would block the ability to type new data to send to other Clients.
I don't won't to labour on this example as it's not the main drive of this article, but I thought it may me worth talking about, just so you can see what sort of things you are up against when you start to use Threads, and how they can actually be helpful
In this section I will discuss some common traps, when working with Threads. This is by no means all the traps, rather some of the most common errors.
If we consider the following code example (attached demo ThreadTrap1 project)
using System;
using System.Threading;
namespace ThreadTrap1
{
/// <summary>
/// This example shows a threading Trap, you simply can't
/// rely on threads executing in the order in which they
/// are started.
/// </summary>
class Program
{
static void Main(string[] args)
{
Thread T1 = new Thread(new ThreadStart(Increment));
Thread T2 = new Thread(new ThreadStart(Increment));
T1.Name = "T1";
T2.Name = "T2";
T1.Start();
T2.Start();
Console.ReadLine();
}
private static void Increment()
{
for (int i = 0; i < 100000;i++ )
if (i % 10000 == 0)
Console.WriteLine("Thread Name {0}",
Thread.CurrentThread.Name);
WriteDone(Thread.CurrentThread.Name);
}
private static void WriteDone(string threadName)
{
switch (threadName)
{
case "T1" :
Console.WriteLine("T1 Finished");
break;
case "T2":
Console.WriteLine("T2 Finished");
break;
}
}
}
}
From looking at this code, one would assume that the Thread named T1 would always finish 1st, as it is the one that is started 1st. However this is not the case, it may finish 1st sometimes, and other times it may not. See the 2 screen shots below that were taken from 2 different runs of this same code
In this screen shot T1 did finish 1st
In this screen shot T2 finished 1st
So this is a trap, never assume the threads run in the order you start them in
If we consider the following code example (attached demo ThreadTrap2 project)
using System;
using System.Threading;
namespace ThreadTrap2
{
/// <summary>
/// This example shows a threading Trap, you simply can't
/// rely on threads executing in the order in which they
/// are started. And also what happens when access to a
/// shared field in not synchronized
/// </summary>
class Program
{
protected static long sharedField = 0;
static void Main(string[] args)
{
Thread T1 = new Thread(new ThreadStart(Increment));
Thread T2 = new Thread(new ThreadStart(Increment));
T1.Name = "T1";
T2.Name = "T2";
T1.Start();
T2.Start();
Console.ReadLine();
}
private static void Increment()
{
for (int i = 0; i < 100000; i++)
if (i % 10000 == 0)
Console.WriteLine("Thread Name {0}, Shared value ={1}",
Thread.CurrentThread.Name, sharedField.ToString());
sharedField++;
WriteDone(Thread.CurrentThread.Name);
}
private static void WriteDone(string threadName)
{
switch (threadName)
{
case "T1":
Console.WriteLine("T1 Finished, Shared value ={0}", sharedField.ToString());
break;
case "T2":
Console.WriteLine("T2 Finished, Shared value ={0}", sharedField.ToString());
break;
}
}
}
}
This code is simliar to the previous example, we still can't rely on the execution order of the threads. Things are also a little worse this time as I introduced a shared field that the 2 threads have access to. It can be seen in the screen shots below that we get different values on different runs of the code. This is failr bad news, imagine this was your bank account. We can solve these issues using "Synchronization" as we will see in a future article in this series.
This screen shot shows the result of 1 run, and we get these end results
This screen shot shows the result of another run, and we get different end results. Oh o, bad news
So this is a trap, never assume threads and shared data play well together, because they don't
Consider the following problem. "The system must send an invioce to each user who has placed an order. This process should run in the background and should not have any adverse affect on the user interface".
If we consider the following code example (attached demo ThreadTrap3 project.)
DONT RUN THIS, ITS JUST TO SHOW A BAD EXAMPLE
using System;
using System.Collections.Generic;
using System.Threading;
namespace ThreadTrap3
{
/// <summary>
/// This code is bad as it starts a new thread for each invoice that it
/// has to send to a Customer. This could be 1000nds of threads, that will
/// all incur some overhead when the CPU has to context switch between the
/// threads.For this example it probably will not occur as the threads work is
/// so small, but for longer running operations there could be issues.
/// </summary>
class Program
{
static void Main(string[] args)
{
List<Customer> custs = new List<Customer>();
custs.Add(new Customer { CustomerEmail = "fred@gmail.com",
InvoiceNo = 1, Name = "fred" });
custs.Add(new Customer { CustomerEmail = "same@gmail.com",
InvoiceNo = 2, Name = "sam" });
custs.Add(new Customer { CustomerEmail = "john@gmail.com",
InvoiceNo = 3, Name = "john" });
custs.Add(new Customer { CustomerEmail = "ted@gmail.com",
InvoiceNo = 4, Name = "ted" });
InvoiceThread.CreateAllInvoices(custs);
Console.ReadLine();
}
}
public class InvoiceThread
{
private static Customer currentCustomer;
public static void CreateAllInvoices(List<Customer> customers)
{
//Create a new thread for every Invoice we need to send. Bad news
foreach (Customer cust in customers)
{
currentCustomer=cust;
Thread thread = new Thread(new ThreadStart(SendCustomerInvoice));
thread.Start();
}
}
private static void SendCustomerInvoice()
{
//Simulate sending an invoice
Console.WriteLine("Send invoice {0}, to Customer {1}",
currentCustomer.InvoiceNo.ToString(),
currentCustomer.Name);
}
}
/// <summary>
/// Simple data class
/// </summary>
public class Customer
{
public string Name { get; set; }
public string CustomerEmail { get; set; }
public int InvoiceNo { get; set; }
}
}
This example is bad as it creates a new thread for every customer that it needs to send an invoice to
foreach (Customer cust in customers)
{
currentCustomer=cust;
Thread thread = new Thread(new ThreadStart(SendCustomerInvoice));
thread.Start();
}
But just why is this so bad? It seems to make some sort of sense, after all it could take quite a while to send a invoice off using email. So why not thread it? When we create a new thread within a loop as I have done here, each thread needs to be allocated some CPU time, and as such teh CPU will spend so much time context switching (A context switch consists of storing context information from the CPU (registers) to the current thread's kernel stack, and loading the context information to the CPU from the kernel stack of the thread selected for execution) to allow each thread some CPU time that very little of the actual thread instructions will be performed, the system may even lock up.
Thats one thing, there is also quite a lot of overhead with creating a thread to begin with. This is why a ThreadPool class exists. We will be seeing that in a later article.
What would make more sense is to have a single background thread and use that to send out ALL the invoices, or use a thread pool, where once a thread it finished it can go back into a shared pool. We will be looking at thread pools in a subsequent article in this series
We have not covered locks yet (part3 will talk about these) so I don't want to spend too much time on this one, but I will just briefly mention them
We can imagine 2 or more threads sharing some common data, which we need to ensure is kept safe, now in .NET there are various ways of doing this, one of which is by using the "lock" keyword (we will cover this in part3) which ensures mutually exclusive access to the code within the "lock" section. One possibly problem may be that a programmer locks an entire method to try and ensure the shared data is kept safe, but really they only needed to lock a couple of lines of code that dealt with the shared data. I think a good decsription I once heard for this called it "Lock Granularity" which I think sums it up quite well. Basically only lock what you really need to lock.
Well thats all I wanted to say this time. Threading is a complex subject, and as such this series will be quite hard, but I think worth a read.
Next time we will be looking at Synchronization.
Could I just ask, if you liked this article could you please vote for it, as that will tell me whether this Threading crusade that I am about to embark on will be worth creating articles for.
I thank you very much
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 10 Aug 2008 Editor: |
Copyright 2008 by Sacha Barber Everything else Copyright © CodeProject, 1999-2009 Web15 | Advertise on the Code Project |