HowTo Thread CSharp II
HowTo Thread CSharp II
Intermediate Level
This article is written for the intermediate and senior C# developer. Working knowledge
of the C# programming language and dotNet 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 invalidate portions of this
article.
In the first article, I discussed how to create threads, thread pools and some of the
synchronization objects available in the System.Threading dotNet namespace. In this
second article, I will complete my discussion of the synchronization objects and will
discuss thread local storage, COM interoperability and thread states.
ReaderWriterLock
Another popular design pattern introduced as a class in the dotNet 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.
namespace ConsoleApplication9
{
class Class1
{
public Class1()
{
rwlock = new ReaderWriterLock();
val = "Writer Sequence Number is 1";
}
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();
}
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).
namespace ConsoleApplication10
{
class Class1
{
public void ThreadStart()
{
Mutex mutex = new Mutex(false, "MyMutex");
mutex.WaitOne();
Console.WriteLine("Hello");
}
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.
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);
}
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
Now what about those COM apartments? How do these new dotNet threads handle COM
apartments? dotNet threads can reside in both single and multithreaded apartments. When
a dotNet 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.
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;
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 dotNet
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
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 dotNet
I figured with all this work I’m doing learning dotNet threads that I would leave you with
an important resource. Table 1 shows my attempt in converting Win32 functions to
dotNet classes and methods.
Win32 dotNet
CreateEvent new System.Threading.Event
CreateMutex new System.Threading.Mutex
CreateSemaphore n/a
CreateThread new System.Threading.Thread and
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
ReleaseMutex System.Threading.Mutex.ReleaseMutex
ReleaseSemaphore n/a
ResetEvent System.Threading.AutoResetEvent.Reset or
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
WaitForSingleObject and System.Threading.Thread.Join or
WaitForSingleObjectEx System.Threading.Monitor.Wait or
System.Threading.WaitHandle.WaitOne
WaitForMultipleObjects and System.Threading.WaitHandle.WaitAll or
WaitForMultipleObjects System.Threading.WaitHandle.WaitAny
If you were to undertake a project of converting a Win32 application to a dotNet
application, then this table could prove very useful. In some cases, a few objects and
methods in the dotNet 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 dotNet threads. This section describes the states of a thread. The Thread object in
the dotNet framework has a property called the ThreadState, which is one of the members
of the following enumeration, which I pulled from the dotNET 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.
Figure 1: State Diagram
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 dotNet 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.
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 dotNet framework is
a thread that does not have impact on whether a process is terminated.
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 dotNet 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 dotNet’s Threading namespace is by far the easiest way to write applications that
require a lot of thread programming. I didn’t find any performance problems with the
dotNet threads, but neither did I find them any faster than other thread libraries available
in C++ or Java threads.