Sunday, April 13, 2008

Threads

How do I spawn a thread?

Create an instance of a System.Threading.Thread object, passing it an instance of a ThreadStart delegate that will be executed on the new thread. For example:

    class MyThread
{
public MyThread( string initData )
{
m_data = initData;
m_thread = new Thread( new ThreadStart(ThreadMain) );
m_thread.Start();
}

// ThreadMain() is executed on the new thread.
private void ThreadMain()
{
Console.WriteLine( m_data );
}

public void WaitUntilFinished()
{
m_thread.Join();
}

private Thread m_thread;
private string m_data;
}

In this case creating an instance of the MyThread class is sufficient to spawn the thread and execute the MyThread.ThreadMain() method:

    MyThread t = new MyThread( "Hello, world." );
t.WaitUntilFinished();

How do I stop a thread?

There are several options. First, you can use your own communication mechanism to tell the ThreadStart method to finish. Alternatively the Thread class has in-built support for instructing the thread to stop. The two principle methods are Thread.Interrupt() and Thread.Abort(). The former will cause a ThreadInterruptedException to be thrown on the thread when it next goes into a WaitJoinSleep state. In other words, Thread.Interrupt is a polite way of asking the thread to stop when it is no longer doing any useful work. In contrast, Thread.Abort() throws a ThreadAbortException regardless of what the thread is doing. Furthermore, the ThreadAbortException cannot normally be caught (though the ThreadStart's finally method will be executed). Thread.Abort() is a heavy-handed mechanism which should not normally be required.

How do I use the thread pool?

By passing an instance of a WaitCallback delegate to the ThreadPool.QueueUserWorkItem() method

    class CApp
{
static void Main()
{
string s = "Hello, World";
ThreadPool.QueueUserWorkItem( new WaitCallback( DoWork ), s );

Thread.Sleep( 1000 ); // Give time for work item to be executed
}

// DoWork is executed on a thread from the thread pool.
static void DoWork( object state )
{
Console.WriteLine( state );
}
}

How do I know when my thread pool work item has completed?

There is no way to query the thread pool for this information. You must put code into the WaitCallback method to signal that it has completed. Events are useful for this.

How do I prevent concurrent access to my data?

Each object has a concurrency lock (critical section) associated with it. The System.Threading.Monitor.Enter/Exit methods are used to acquire and release this lock. For example, instances of the following class only allow one thread at a time to enter method f():

    class C
{
public void f()
{
try
{
Monitor.Enter(this);
...
}
finally
{
Monitor.Exit(this);
}
}
}

C# has a 'lock' keyword which provides a convenient shorthand for the code above:

    class C
{
public void f()
{
lock(this)
{
...
}
}
}

Note that calling Monitor.Enter(myObject) does NOT mean that all access to myObject is serialized. It means that the synchronisation lock associated with myObject has been acquired, and no other thread can acquire that lock until Monitor.Exit(o) is called. In other words, this class is functionally equivalent to the classes above:

    class C
{
public void f()
{
lock( m_object )
{
...
}
}

private m_object = new object();
}

Actually, it could be argued that this version of the code is superior, as the lock is totally encapsulated within the class, and not accessible to the user of the object.

Should I use ReaderWriterLock instead of Monitor.Enter/Exit?

Maybe, but be careful. ReaderWriterLock is used to allow multiple threads to read from a data source, while still granting exclusive access to a single writer thread. This makes sense for data access that is mostly read-only, but there are some caveats. First, ReaderWriterLock is relatively poor performing compared to Monitor.Enter/Exit, which offsets some of the benefits. Second, you need to be very sure that the data structures you are accessing fully support multithreaded read access. Finally, there is apparently a bug in the v1.1 ReaderWriterLock that can cause starvation for writers when there are a large number of readers.

Ian Griffiths has some interesting discussion on ReaderWriterLock here and here.

No comments: