Multithreading: creating threads

In the last post, we’ve made a quick introduction to the concept of a thread. Today we’re going to keep talking about threads, but we’re going to start looking at some code. As we’ve seen, all processes have (at least!) one thread that is automatically created whenever the process starts running. We’ve also seen that it’s possible to create other (secondary) threads. Today we’ll see how we can do that in managed code.

In managed code, a thread is represented by an instance of the type System.Threading.Thread. The following snippet shows how we can get a reference to the current executing thread:

var currentThread = Thread.CurrentThread;
Console.WriteLine( "IsBackground: {0}",
                                         currentThread.IsBackground);
Console.WriteLine( "IsThreadPool: {0}",
                                         currentThread.IsThreadPoolThread);

You can use the static CurrentThread property to get a reference to the thread that is executing the code. If you drop the previous line inside a simple console app with a single thread (ie, it only has the main thread), then you should get false for the IsBackground and IsThreadPoolThread properties. We’ll come back to these properties in the future. For now, just keep in mind that a thread might be a background or a foreground thread and that a background thread doesn’t prevent a process from exiting (when the main thread reaches the end of its processing, that is).

As we’ll see in the future, all managed processes have a default pool of threads which you can use (this generally leads to better performance that creating multiple threads because threads are reused when they finish their work). When the thread belongs to that managed thread pool,then the IsThreadPoolThread has its value set to true.

Besides getting references to existing threads,we can also create new threads. Threads are created by using one of the following constructors of the Thread class:

public Thread(ParameterizedThreadStart start);
public Thread(ThreadStart start);
public Thread(ParameterizedThreadStart start, int maxStackSize);
public Thread(ThreadStart start, int maxStackSize);

As you can see, all constructors receive a reference to a delegate of type ParameterizedThreadStart or ThreadStart. Here are the signatures of those delegates:

public delegate void ParameterizedThreadStart(object obj);
public delegate void ThreadStart();

The main difference between these delegates is that the ParameterizedThreadStart lets you pass an object (while the other doesn’t receive any parameters). If you look at the previous constructors, you’ll surely notice that there are also two overloads that let you pass the stack’s maximum size. You can use these constructors to specify the maximum stack size that should be allocated to the current thread. This is really an interesting topic and we’ll return to it in the next post.

In order to illustrate the creation of a new thread, let’s write  a simple example that prints all numbers from 0 to 1000 in a secondary thread. As we’ve seen, the first thing we need to do is create a new thread instance:

var thread1 = new Thread( () => {
                    foreach (var i in Enumerable.Range(0, 10000)) {
                           Console.WriteLine(i);
                    }
               });

If you drop the previous line in a console app and run it, you won’t see anything! Even though thread1 is an instance of the Thread type (which wraps the unmanaged kernel thread object), it’s important to understand that the wrapped OS thread will only be created when we invoke the Start method over that instance. So, in order to see anything, we need to have the following code:

var thread1 = new Thread( ( )=> {
                    foreach (var i in Enumerable.Range(0, 10000)) {
                           Console.WriteLine(i);
                    }
               });

thread1.IsBackground = true; //1. setting it to background thread
thread1.Start(); //2. effectively creating the OS wrapped thread
thread1.Join(); //3. wait for thread1 to complete: synching in thread object

In the previous example we’re setting the IsBackground property to true to show you that if we don’t wait for it to complete, then we won’t effectively see anything (notice the Join method call – waiting on a thread is only possible because OS threads are associated with a kernel object. When the thread ends executing the code, it gets into the signaled and that awakes our main thread).

If we wanted, we could also have used the “parameterized” delegate in order to pass a value to the method that is executed in the secondary thread. The following snippet shows how you can do that:

var thread1 = new Thread( obj => {
                    var top = Convert.ToInt32(obj);
                    foreach (var i in Enumerable.Range(0, top)) {
                           Console.WriteLine(i);
                    }
               });

thread1.IsBackground = true;
thread1.Start(100); //print until 100 only
thread1.Join();

Really simple, right?

Besides starting threads, you can also end them in several ways. The recommended way is to reach the last instruction in the IP pointer. For instance, in the previous example this means something like reaching the closing } of the Lambda expression.

In the managed world, you can also end a thread by calling the Ab
ort
method. In the current release, there are two overloads:

public void Abort();
public void Abort(object stateInfo);

According to Joe Duffy’s master piece, calling this method results in having the runtime introducing an exception at the current IP. This is good because it allows finally blocks to run (which means that you’ll be able to clean resources when faced with this exception). The most important thing about this exception is that the runtime is aware of certain regions of code  that are performing uninterruptable operations and will delay the introduction of the exception until the IP exits those regions.

The generated exception will be of type ThreadAbortException and if you used the overload that receives a state parameter, then that object is available on the ExceptionState property of that class.

As you might expect, you can catch this exception but that won’t be enough to swallow it. If you really need to cancel the thread abort, you’ll have to use the static ResetAbort method from within the catch block. Try commenting the following ResetAbort method call and see what happens when you runt the following snippet:

var thread1 = new Thread(obj => {
                                     try {
                                           var top = Convert.ToInt32(obj);
                                           foreach (var i in Enumerable.Range(0, top)) {
                                              Console.WriteLine(i);
                                           }

                                    }
                                    catch (ThreadAbortException) {
                                          Thread.ResetAbort();
                                    } 
                                    Console.WriteLine("After catch");

});
thread1.IsBackground = true;
thread1.Start(100);
Thread.Sleep(10); //just to see what’s happening
thread1.Abort();
thread1.Join();

If you comment the ResetAbort call, the “After catch” message won’t be printed.

Besides stopping a thread (through the Abort  method call), you can also suspend a thread by calling the Suspend method. Doing this removes it from the processor (if it’s running) and from the queue managed by the thread scheduler. As with all things you do with a thread, you should take extra care and should never use this method for synchronization! (you don’t really know what a thread is doing when you suspend it, right?)

You shouldn’t also forget to call Resume for resuming it (ie, for adding it back to the queue of threads that will be run in the future). A valid scenario for calling Suspend is getting the stack trace of an existing thread (in fact, you should always suspend the thread before getting that info):

//same as code presented in earlier snippets
thread1.Start(100);
Thread.Sleep(10);
thread1.Suspend();
var trace = new StackTrace(thread1, true); //true: get code line number
thread1.Resume();
Console.WriteLine(trace.ToString());
thread1.Join();

And I guess that’s all for today. More about multithreaded programming in the next posts.

Advertisements

~ by Luis Abreu on May 4, 2009.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: