Sunteți pe pagina 1din 16

Dot Net Threading, Part I

by Randy Charles Morin


Page 1 of 16

Intermediate Level
This article is written for the intermediate and senior C# developer. Working knowledge of the C# programming language and Dot
Net framework is assumed. The article was written with a Beta version of VS.NET and associated documentation. Changes,
although not anticipated, might occur before final release of VS.NET that invalidates portions of this article.

Creating Threads
Creating a thread in C# is close to trivial, but not quite. The only non-trivial thing about creating a thread is Dot Net delegate-
classes. Let me explain in few words what is a delegate class. The delegate is a wrapper around a code construct in the Dot Net.
The code construct could be an object instance, an instance method or a static method. Delegates are used when you want to
pass one of the three code constructs as a parameter to another method. When creating a new thread you have to use the
ThreadStart delegate class to wrap the instance method that will be executed in the newly created thread. The instance method
must return void and must not have any parameters.

void ThreadStart()

To create a new thread, first create a new ThreadStart object, passing the instance method of the thread procedure in the
constructor. The new delegate object is then passed to the constructor of the Thread.

Thread thread = new Thread (new ThreadStart (obj.ThreadStart));

You’ve now created a new thread, but the thread is not yet started. To start the thread, you call the Thread.Start instance
method.

thread.Start();

And that’s it. You have a new running thread. A complete console application that creates a thread and outputs a couple
messages to the console window is shown in Listing 1.

Listing 1: Creating Threads


using System;
using System.Threading;
namespace ConsoleApplication1
{
 class Class1
 {
    static void PrintHelloFromThreadName()
{
  Console.WriteLine("Hello, from thread {0}", Thread.CurrentThread.Name); // {0}
}

    public void ThreadStart()
{
  PrintHelloFromThreadName();
}
     static void Main(string[] args)
{
  Thread.CurrentThread.Name = "Main thread";
  Class1 obj = new Class1();
  Thread thread = new Thread( new ThreadStart(obj.ThreadStart));
Dot Net Threading, Part I
by Randy Charles Morin
Page 2 of 16
  thread.Name = "Forked thread";
  thread.Start();
  PrintHelloFromThreadName();
}
   }
}

A nice feature of Dot Net threads, and for that matter any Dot Net object, is the ability name the object. If you name your
threads, then the debugger will pick up those names and you’ll have a much easier time debugging (see Figure 1).
The frame in the bottom left of the IDE window in Figure 1 shows all the threads in out C# application. I set a breakpoint in the
PrintHelloFromThread Name static method in Listing 1 and ran the application. When the application stops on the breakpoint, I
called up the threads window from the menu bar, Debug | Window | Threads. As you can see, the Name in the threads window of
the IDE is the same as the name given the Thread object in our C# code.

Thread Pools
I was very impressed when I found out that the Dot Net framework library included the “System.Threading.ThreadPool” class. I
was also impressed by how easy it was to use. You need not create the pool of threads, nor do you have to specify how many
consuming threads you require in the pool. The ThreadPool class handles the creation of new threads and the distribution of the
wares to consume amongst those threads. You can kick off a consuming thread pool by simply invoking the
ThreadPool.QueueUserWorkItem static method.

ThreadPool.QueueUserWorkItem( new WaitCallback(Consume), ware);

Ware: For the rest of this article I define a ware to be an item that is produced by the producing thread and
consumed by a consuming thread in the consumer producer design pattern. This is a very narrow definition
of the word, but one that suits this article.

The parameters of the QueueUserWorkItem static method are the WaitCallback delegate that wraps the instance method used in
consuming your ware and the ware that you are passing to the method. Your consuming instance method must return void and
take one object parameter. The ware that is passed to the QueueUserWorkItem method will be passed into your consuming
instance method as the one object parameter.

public void Consume(Object obj)

Again, the simplicity of C# and the Dot Net framework shine through. In just a few lines of code, I’ve recreated a multithreaded
consumer-producer application (see Listing 2).

Listing 2: Creating Thread Pools


using System;
using System.Threading;
using System.Diagnostics;
namespace ConsoleApplication2
{
  public class Ware
 {
   public int id;
   public Ware(int _id)
{
Dot Net Threading, Part I
by Randy Charles Morin
Page 3 of 16
  id = _id;
}
 }

 class Class1
 {
   public int QueueLength;
   public Class1()
    {
      QueueLength = 0;
    }
   public void Produce(Ware ware)
    {
       ThreadPool.QueueUserWorkItem(new WaitCallback(Consume), ware);
       QueueLength++;
    }

   public void Consume(Object obj)
    {
      Console.WriteLine("Thread {0} consumes {1}",
      Thread.CurrentThread.GetHashCode(), //{0}((Ware)obj).id); //{1}
      Thread.Sleep(100);
      QueueLength­­;
    }
   
   public static void Main(String[] args)
    {
      Class1 obj = new Class1();
      for (int i = 0; i < 1000; i++)
 {
   obj.Produce(new Ware(i));
 }
      Console.WriteLine("Thread {0}",
      Thread.CurrentThread.GetHashCode() ); //{0}
      while (obj.QueueLength != 0)
       {
         Thread.Sleep(1000);
       }
    }
  }
}

I added the line Thread.Sleep(100) in the Consume method to simulate the processing that a consumer would normally have
performed on the ware. If I didn’t include this Sleep’ing, then one consumer thread could have handled all 100 wares. The
additional Sleep’ing forces the Dot Net framework to create additional threads and more accurately portrays the features of the
ThreadPool class.
Dot Net Threading, Part I
by Randy Charles Morin
Page 4 of 16

Synchronization Objects
The previous code contains some rather inefficient coding when the main thread cleans up. I repeatedly test the queue length
every second until the queue length reaches zero. This may mean that the process will continue executing for up to a full second
after the queues are finally drained. Wow! I can’t have that. OK! Maybe that’s not a good reason to change the code, but it is a
convenient excuse for me to introduce you to the System.Threading.ManualResetEvent class. Using a ManualResetEvent object, I
could trigger the main thread to complete as soon as the last ware was consumed. I’ll do this by creating two new instance data
members, a bool WaitForComplete to tell us when the main thread is waiting to exit and a ManualResetEvent Event object that
will signal the main thread to exit (see Listing 3).

Listing 3: Using Events


private bool WaitForComplete;
private ManualResetEvent Event;

public void Wait()
 {
   if (QueueLength == 0)
    {
      return;
    }
  Event = new ManualResetEvent(false);
  WaitForComplete = true;
  Event.WaitOne();
 }

public void Consume(Object obj)
 {
   Console.WriteLine("Thread{0}consumes {1}",Thread.CurrentThread.GetHashCode(), //{0}((Ware)obj).id); //{1}
   Thread.Sleep(100);
   QueueLength­­;
   if (WaitForComplete)
    {
      if (QueueLength == 0)
        {
          Event.Set();
        }
    };
  }

When the consuming thread finishes consuming a ware and detects that the WaitForComplete is true, it will trigger the Event
when the queue length is zero. Instead of calling the while block when it wants to exit, the main thread calls the Wait instance
method. This method sets the WaitForComplete flag and waits on the Event object. Let me test your threading prowess. The
previous listing contained a race condition. Can you find it? Take a minute or two before continuing. Tic! Tic! Tic!

Race Condition
A race condition is a bug caused by an incorrect assumption as to the timing of two events, that is, that one event would always
occur before the other. The race condition occurs when the system shuts down. If the main thread is swapped out in the Wait
instance method between testing if the queue length is zero and setting the WaitForComplete flag to true and then the last
Dot Net Threading, Part I
by Randy Charles Morin
Page 5 of 16
consuming thread exits the Consume instance method while the main thread is in this state, the event will never be triggered. I
ran the code a few hundred times and was never able to trigger the condition. You can’t reproduce it because the main thread
should be waiting on the event object well before the last consuming-thread exits.

Monitor and Lock


I could have arranged the code otherwise to prevent this race condition, but now I’ve created another opportunity to introduce
you to the System.Threading.Monitor class and the lock C# construct. The monitor design pattern is most familiar to Java
developers. In Java, the synchronized keyword allowed the developer to create quick critical sections within their code. The Java
construct was often called a monitor. The Dot Net framework presents a similar class called the Monitor that implements
traditional wait and signal methods called Wait and Pulse. The C# compiler uses this Monitor class to implement a language
construct called a lock. The lock is established on an object and while the lock is established, nobody else can acquire the lock
and must wait till the lock is freed. I used this lock construct to prevent our previous race condition (see
Listing 4).

Listing 4: Using Monitors


public void Wait()
  {
    lock (this)
     {
        if (QueueLength == 0)
          {
             return;
          }
        Event = new ManualResetEvent(false);
        WaitForComplete = true;
     }
    Event.WaitOne();
   }

public void Consume(Object obj)
   {
       Console.WriteLine("Thread {0} consumes {1}",Thread.CurrentThread.GetHashCode(), //{0}
                       ((Ware)obj).id); //{1}
       Thread.Sleep(100);
       lock (this)
          {
             QueueLength­­;
             if (!WaitForComplete)
                {
                  return;
                }
          }
       if (QueueLength == 0)
          {
             Event.Set();
          };
Dot Net Threading, Part I
by Randy Charles Morin
Page 6 of 16
  }

Preventing the concurrent setting and testing of the queue length and WaitForComplete flag by two different threads removes the
race condition. The lock ensures that the setting and testing of these two variables is essentially atomic.

Join
Before Dot Net, I was often asked questions about how to wait for a Win32 thread to exit. The solution was to acquire a handle to
the thread and wait on the handle. Or alternatively, you could setup an event that was triggered at the end of the thread and wait
on that event. Dot Net provides us with a simpler method of doing the same. If you call the Thread.Join instance method, then
the current thread will wait until the thread represented by the Thread object is terminated (see Listing 5).

Listing 5: Using Join


using System;
using System.Threading;
using System.Diagnostics;
namespace ConsoleApplication7
{

  class Class1
   {

     public void Pump()
       {
         for (int i=0;i<100;i++)
           {
             Console.WriteLine("Value {0}", i);
             Thread.Sleep(1);
Dot Net Threading, Part I
by Randy Charles Morin
Page 7 of 16
           }
        }

    static void Main(string[] args)
       {
          Class1 obj = new Class1();
          Thread pump = new Thread(new ThreadStart(obj.Pump));
          pump.Start();
          Thread.Sleep(500); // force the other thread
         // thru a couple iterations
         pump.Join(); // wait until the thread is completed
         Console.WriteLine("Goodbye");
      }
   }
}

In this previous listing, the main thread creates a new thread (pump), then waits for the thread to complete by calling the
pump.Join instance method. If you run the previous code, as is, then the output will be the numbers 0 to 99 and finally the word
Goodbye. If you remove the call to pump.Join, then the Goodbye message may be printed before the last number. I chose to put
the main thread to sleep for half a second as this displayed the Goodbye message in the middle of the stream of numbers (when
pump.Join was removed).

AutoResetEvent & Timer


Early in the article, I introduced you to the ManualResetEvent class. This class allowed you to set and reset (signal and unsignal)
the event by calling the Set and Reset instance methods. The System.Threading.AutoResetEvent class is very similar to the
ManualResetEvent class, but when a thread waiting on the event is signaled, the one thread is released and the event is returned
to the unsignaled state. This removes the necessity to reset the signal after a thread is signaled. Another great class in the
System.Threading namespace is the Timer class. This class allows you to signal an event at a particular interval in time in the
future. The Timer class is implemented using a delegate callback instance method. When the Timer is signaled, the class calls the
instance method that you specified in the constructor of the Timer object. The Timer callback can also receive a parameter object
passed in the call to the Timer constructor. Presented in Listing 6 is a small sample using the AutoResetEvent and Timer classes.

Listing 6: AutoResetEvent and Timer Class


using System;
using System.Threading;
namespace ConsoleApplication8
{
  class Class1
    {
      
        public void TimerCallback(Object obj)
          {
             Console.WriteLine("Timer triggered");
             ((AutoResetEvent)obj).Set();
             Thread.Sleep(1000);
             ((AutoResetEvent)obj).Set();
          }
Dot Net Threading, Part I
by Randy Charles Morin
Page 8 of 16

       static void Main(string[] args)
         {
             Class1 obj = new Class1();
             AutoResetEvent ev = new AutoResetEvent(false);
             Timer timer = new Timer(new TimerCallback(obj.TimerCallback), ev, 1000, 0);
             ev.WaitOne();
             Console.WriteLine("Event Fired");
             ev.WaitOne();
             Console.WriteLine("Event Fired");
        }
    }
}

Note that the Timer callback instance method is wrapped in a TimerCallback delegate object. The main thread will create an
AutoResetEvent object and a Timer object. The main thread then waits on the event object. The TimerCallback instance method
is called after one second, triggering the event object. Because the event object is automatically reset, when the main thread
attempts to wait on the event again, the thread yields until the event is signaled a second time. The TimerCallback instance
method waits another second and then signals the event a second time, releasing the main thread.

ReaderWriterLock
Another popular design pattern introduced as a class in the Dot Net framework is the ReaderWriterLock. This class allows an
unlimited amount of read locks or one write lock, but not both. This allows anyone to read the protected resource, as long as
nobody is writing to the protected resource and allows only one thread to write to the protected resource at any one time.
Listing 1 presents a sample using the ReaderWriterLock class.

Listing 1: ReaderWriterLock Class


using System;
using System.Threading;
namespace ConsoleApplication9
{
class Class1
  {
    public Class1()
{
rwlock = new ReaderWriterLock();
val = "Writer Sequence Number is 1";
}
    private ReaderWriterLock rwlock;
   private string val;
   public void Reader()
{
rwlock.AcquireReaderLock(Timeout.Infinite);
Console.WriteLine("Acquired Read Handle: "
"Value = {0}", val);
Thread.Sleep(1);
Console.WriteLine("Releasing Read Handle");
Dot Net Threading, Part I
by Randy Charles Morin
Page 9 of 16
rwlock.ReleaseReaderLock();
}
public void Writer()
{
rwlock.AcquireWriterLock(Timeout.Infinite);
Console.WriteLine("Acquired Write Handle");
int id = rwlock.WriterSeqNum;
Console.WriteLine("Writer Sequence Number is {0}", id);
Thread.Sleep(1);
val = "Writer ";
Thread.Sleep(1);
val += "Sequence ";
Thread.Sleep(1);
val += "Number ";
Thread.Sleep(1);
val += "is ";
Thread.Sleep(1);
val += id;
Console.WriteLine("Releasing Write Handle");
rwlock.ReleaseWriterLock();
}
static void Main(string[] args)
{
Class1 obj = new Class1();
const int n = 1000;
Thread[] reader = new Thread[n];
Thread[] writer = new Thread[10];
for (int i=0;i<n;i++)
{
   reader[i] = new Thread(new ThreadStart(obj.Reader));
    if (i<10)
{
  writer[i] = new Thread (new ThreadStart(obj.Writer));
};
}
for (int i=0;i<n;i++)
{
reader[i].Start();
if (i<10)
{
writer[i].Start();
};
}
}
    }
 }
Dot Net Threading, Part I
by Randy Charles Morin
Page 10 of 16
In the above listing, I create 10 writer threads and 1000 reader threads. I parameterized the number of reader threads so that I
could quickly trigger different behaviors in the code by modifying the number of reader threads. Once the threads are started
they attempt to acquire read and write lock on the ReaderWriterLock object. If you run the code, then you can see the writer
threads have a difficult time acquiring write locks. I tried to put as many small sleep statements as I could to force the threads to
swap out of memory earlier than they would have normally.

Mutex
The last synchronization object I’ll present here is the Mutex. The most useful feature of the Mutex class is that it may be named.
This allows you to create two Mutex objects in different areas of code without having to share Mutex object instances. As long as
the Mutex object instances have the same name, they will synchronize with each other. You could create the Mutex in two
different processes on the same machine and the synchronization crosses the process boundary. Nor do you have to worry about
passing the Mutex object in order to share the synchronization object between two threads or methods (see Listing 2).

Listing 2: Mutex Class


using System;
using System.Threading;
namespace ConsoleApplication10
{
  class Class1
   {
      public void ThreadStart()
{
Mutex mutex = new Mutex(false, "MyMutex");
mutex.WaitOne();
Console.WriteLine("Hello");
}
static void Main(string[] args)
{
  Class1 obj = new Class1();
  Thread thread = new Thread( new ThreadStart(obj.ThreadStart));
  Mutex mutex = new Mutex(true, "MyMutex");
  thread.Start();
  Thread.Sleep(1000);
  Console.WriteLine("Signal");
  mutex.ReleaseMutex();
        }
    }
}

In the above listing, two separate Mutex objects are created, but the Mutex class allows the two instances to interact. The Signal
will always precede the Hello in the output of this program. This is because the Mutex in the thread is created with the lock
acquired. The second thread then creates the Mutex without acquiring the lock. The second thread will then wait on the mutex
until the main thread releases the mutex a second later.

Thread Local Storage


The Thread class and System.Threading namespace also contain some methods and classes for realizing thread local storage.
Thread local storage is a manner of storing data in a container that is unique to the thread. Many threads could then use the
same named container to store their data without concern of collision. Each thread’s local storage is distinct from another
Dot Net Threading, Part I
by Randy Charles Morin
Page 11 of 16
thread’s local storage and is only available in the one thread. Listing 3 shows a small sample using the thread-local-storage
methods and classes.

Listing 3: Thread Local Storage


using System;
using System.Threading;
namespace ConsoleApplication11
{
class Class1
  {
     public void ThreadStart()
       {
          string str1 = "My Cookie " + Thread.CurrentThread.GetHashCode();
          Console.WriteLine("worker thread: {0}",str1);
          LocalDataStoreSlot lds = Thread.GetNamedDataSlot("COOKIE");
          Thread.SetData(lds, str1);
Thread.Sleep(1);
LocalDataStoreSlot lds2 = Thread.GetNamedDataSlot("COOKIE");
string str2 = "";
str2 = (string)Thread.GetData(lds2);
Console.WriteLine("worker thread: {0}",str2);
      }

     static void Main(string[] args)
{
string str1 = "My Cookie " + Thread.CurrentThread.GetHashCode();
Console.WriteLine("main thread: {0}", str1);
LocalDataStoreSlot lds = Thread.AllocateNamedDataSlot("COOKIE");
Thread.SetData(lds, str1);
Class1 obj = new Class1();
Thread thread = new Thread( new ThreadStart(obj.ThreadStart));
thread.Start();
Thread.Sleep(1);
LocalDataStoreSlot lds2 = Thread.GetNamedDataSlot("COOKIE");
string str2 = "";
str2 = (string)Thread.GetData(lds2);
Console.WriteLine("main thread: {0}", str2);
}
}
}

You could also create and start more than one thread and the behavior of the thread local storage becomes more obvious. I have
played with Win32 thread-local-storage functions and created my own for portability to UNIX, but I have rarely found them very
useful. I strongly believe in stateless computing and thread-local-storage contradicts this belief.

COM Interoperability
Dot Net Threading, Part I
by Randy Charles Morin
Page 12 of 16
Now what about those COM apartments? How do these new Dot Net threads handle COM apartments? Dot Net threads can reside
in both single and multithreaded apartments. When a Dot Net thread is first started it exists neither in a single-threaded or
multithreaded apartment. A static state variable Thread.CurrentThread.Apartment indicates the current apartment type. If you
run the code in Listing 4, then the apartment type will be Unknown, as the thread would not have entered an apartment yet.

Listing 4: Threading Model Attributes


using System;
using System.Threading;
namespace ConsoleApplication5
{
class Class1
{
// line output  // Unknown
// [STAThread] // STA
// [MTAThread] // MTA
public static void Main(String[] args)
{
   Console.WriteLine("Apartment State = {0}",Thread.CurrentThread.ApartmentState);
}
}
}

If you uncomment the line with the STAThread attribute, then the thread set its ApartmentState to STA. If you uncomment the
line with the MTAThread attribute, then the thread set its ApartmentState to MTA. This allows control over the apartment type,
similar to CoInitializeEx. You can also set the ApartmentState static member directly (see Listing 5).

Listing 5: ApartmentState
using System;
using System.Threading;
namespace ConsoleApplication6
{
class Class1
{
static void Main(string[] args)
{
// Thread.CurrentThread.ApartmentState =  ApartmentState.STA;
Thread.CurrentThread.ApartmentState = ApartmentState.MTA;
Console.WriteLine("Apartment State = {0}", Thread.CurrentThread.ApartmentState);
}
}
}

Setting the ApartmentState property has the same affect as using the STAThread and MTAThread attributes.
There are also class attributes that affect the threading model used by the dot Net framework. The ThreadAffinity and
Synchronization class attributes can be used to synchronize access to a class and its instance members.

[ThreadAffinity()]
public class Class1 : ContextBoundObject
[Synchronization()]
public class Class1 : ContextBoundObject
Dot Net Threading, Part I
by Randy Charles Morin
Page 13 of 16

When calling into such classes, the calls are serialized to limit access to the class to one thread at any one time. At this point,
these class context attributes are really thin on documentation. So, I’ll save a more in-depth explanation that may be incorrect
anyway.

Win32 to Dot Net


I figured with all this work I’m doing learning Dot Net threads that I would leave you with an important resource. Table 1 shows
my attempt in converting Win32 functions to Dot Net classes and methods.
Table 1: Converting Win32 to Dot Net

Win32 Dot Net

CreateEvent new System.Threading.Event

CreateMutex new System.Threading.Mutex


CreateSemaphore n/a
new System.Threading.Thread and
CreateThread
new System.Threading.ThreadStart
CreateWaitableTimer new System.Threading.Timer
InitializeCriticalSectiona
lock (C#)
EnterCriticalSection
System.Threading.Monitor
LeaveCriticalSection
DeleteCriticalSection
InterlockedCompareExchange System.Threading.Interlock.CompareExchange

InterlockedDecrement System.Threading.Interlock.Decrement

InterlockedExchange System.Threading.Interlock.Exchange
InterlockedIncrement System.Threading.Interlock.Increment

OpenEvent n/a

OpenMutex new System.Threading.Mutex

OpenSemaphore n/a

OpenWaitableTimer n/a
PulseEvent
n/a
System.Threading.Mutex.ReleaseMutex
ReleaseMutex

ReleaseSemaphore n/a
System.Threading.AutoResetEvent.Reset or
ResetEvent System.Threading.ManualResetEvent.Reset

ResumeThread System.Threading.Thread.Resume

SetEvent System.Threading.AutoResetEvent.Set or
System.Threading.ManualResetEvent.Set
SetWaitableTimer n/a

Sleep System.Threading.Thread.Sleep

SuspendThread System.Threading.Thread.Suspend
TerminateThread System.Threading.Thread.Abort

System.Threading.Thread.Join or
WaitForSingleObject and WaitForSingleObjectEx System.Threading.Monitor.Wait or
System.Threading.WaitHandle.WaitOne

WaitForMultipleObjects and WaitForMultipleObjects System.Threading.WaitHandle.WaitAll or


System.Threading.WaitHandle.WaitAny
Dot Net Threading, Part I
by Randy Charles Morin
Page 14 of 16

If you were to undertake a project of converting a Win32 application to a Dot Net application, then this table could prove very
useful. In some cases, a few objects and methods in the Dot Net framework could closely emulate a Win32 function. I had to, on
occasion, decide how closely they matched and sometimes decided that a match was not appropriate. As an example, you could
create a semaphore with a Mutex object and a counter. But I wouldn’t say it’s a close match, so I didn’t mention these instances.
In other cases, I had to decide between two matches.

Thread States
The last few topics in this article are really just the few bits of reference information I dug up on Dot Net threads. This section
describes the states of a thread. The Thread object in the Dot Net framework has a property called the ThreadState, which is one
of the members of the following enumeration, which I pulled from the Dot Net documentation.
public enum ThreadState
{
Running = 0, SuspendRequested = 2, Background = 4, Unstarted = 8, WaitSleepJoin = 32,
Suspended = 64, AbortRequested = 128, Aborted = 256
};

Unfortunately, I have been able to generate ThreadState’s that are not in this enumeration. Specifically, the Stopped ThreadState
seems to be missing and is easy to generate. If you check the state of a thread that has run to completion, then the state is
marked as Stopped. What I also found is that it is quite easy to generate dual states. You can be in the AbortRequested state and
the WaitSleepJoin state. If you catch the ThreadAbortException and then call Thread.Sleep, then the ThreadState will be
“WaitSleepJoin, AbortRequested”, a dual state. The same is true if you are sleeping when the Suspend instance method is called.
Immediately after the call to the Suspend instance method, the ThreadState property reports “SuspendRequested,
WaitSleepJoin”, then quickly changes to “WaitSleepJoin, Suspended”. I’ve encountered a few state diagrams that tried to depict
the state transitions of Dot Net threads. I must say that most are misleading or incomplete. The biggest problem is that most of
the state diagrams did not attempt to account for dual states. My own attempt at the state diagram, I know, is still lacking but
much further along then anything else I’ve seen (see Figure 1).

Background Threads
There is still a lot missing from the state diagram. Specifically, what happens when you Suspend (), Wait (), Join (), Sleep (),
Abort() a background thread. I’m not going to confuse the diagram to explain these new states. Rather, let me explain that a
thread is either a background thread or a foreground thread. Actions on a background thread are equivalent to actions on a
foreground thread, except in one respect, which I will explain in the next paragraph. So, if you attempt to suspend a running
background thread, then it will move to the SuspendRequested state, then to the Suspended state and finally back to the
Background state, in the same manner as a foreground thread.
Dot Net Threading, Part I
by Randy Charles Morin
Page 15 of 16

Figure 1: State Diagram

The difference between a background thread and a foreground thread is pretty simple. When the last foreground thread of a
process is stopped, then the process terminates. There could be zero, 1 or an infinite number of background threads and they
have no vote in whether a process terminates or not. So when the last foreground thread stops, then all background threads are
also stopped and the process is stopped. I’ve seen quite a few dot-NET programmers incorrectly use the background thread to
mean any thread created using the Thread constructor. The terminology is therefore getting very confusing. The correct meaning
of background thread in Dot Net framework is a thread that does not have impact on whether a process is terminated.

Thread Safe Objects and Types


Here’s a rather interesting tidbit of news. Many of the Dot Net objects and types are thread-safe. The first time I heard that I was
rather confused at what it could mean. Does this mean an increment (++) operation on a C# integer is atomic? I put together a
small piece of C# code that launched a thousand threads and incremented and decremented one integer a million times per
thread. I structured the code to swap the threads like mad to try and create a race condition that would invalidate the operations
on the integer. I was unsuccessful in generating incorrect results. So, I assume the operation is atomic. But I don’t have any
proof (beyond proof-by-example) that it is an atomic operation.

Interlocked
Throughout this article, I have written code that assumes that some operations on C# objects and types are atomic. I would
never suggest writing such code in a production environment. In such an environment, you will have to fall back onto our old
InterlockedIncrement and InterlockedDecrement friends. In C#, these are in the System.Threading.Interlocked class. The class
has two static methods Interlocked.Increment and Interlocked.Decrement. Use them well.

Conclusion
I started this trek into Dot Net threads for one reason. I wanted to evaluate them as a possible alternative for servers that
require a lot of thread programming. What I found was that Dot Net’s Threading namespace is by far the easiest way to write
Dot Net Threading, Part I
by Randy Charles Morin
Page 16 of 16
applications that require a lot of thread programming. I didn’t find any performance problems with the Dot Net threads, but
neither did I find them any faster than other thread libraries available in C++ or Java threads.

S-ar putea să vă placă și