Dec 10

Threading in C# Part -3

Threading in C# Part -3

Threading in C# Part -3

Creating and Starting Threads

As we saw in previous Posts Threading in C# Part -1, Threading in C# Part -2, threads are created using the Thread class’s constructor, passing in a ThreadStart delegate which indicates where execution should begin. Here’s how the ThreadStart delegate is defined:

Calling Start on the thread then sets it running. The thread continues until its method returns, at which point the thread ends. Here’s an example, using the expanded C# syntax for creating a TheadStart delegate:

In this example, thread t executes Go() — at (much) the same time the main thread calls Go(). The result is two near-instant hellos.

A thread can be created more conveniently by specifying just a method group — and allowing C# to infer the ThreadStart delegate:

Another shortcut is to use a lambda expression or anonymous method:

Passing Data to a Thread

The easiest way to pass arguments to a thread’s target method is to execute a lambda expression that calls the method with the desired arguments:

With this approach, you can pass in any number of arguments to the method. You can even wrap the entire implementation in a multi-statement lambda:

You can do the same thing almost as easily in C# 2.0 with anonymous methods:

Another technique is to pass an argument into Thread’s Start method:

This works because Thread’s constructor is overloaded to accept either of two delegates:

The limitation of ParameterizedThreadStart is that it accepts only one argument. And because it’s of type object, it usually needs to be cast.

Lambda expressions and captured variables

As we saw, a lambda expression is the most powerful way to pass data to a thread. However, you must be careful about accidentally modifying captured variables after starting the thread, because these variables are shared. For instance, consider the following:

The output is nondeterministic! Here’s a typical result:

The problem is that the i variable refers to the same memory location throughout the loop’s lifetime. Therefore, each thread calls Console.Write on a variable whose value may change as it is running!
The solution is to use a temporary variable as follows:

Variable temp is now local to each loop iteration. Therefore, each thread captures a different memory location and there’s no problem. We can illustrate the problem in the earlier code more simply with the following example:

Because both lambda expressions capture the same text variable, t2 is printed twice:

Naming Threads

Each thread has a Name property that you can set for the benefit of debugging. This is particularly useful in Visual Studio, since the thread’s name is displayed in the Threads Window and Debug Location toolbar. You can set a thread’s name just once; attempts to change it later will throw an exception.

The static Thread.CurrentThread property gives you the currently executing thread. In the following example, we set the main thread’s name:

Foreground and Background Threads

By default, threads you create explicitly are foreground threads. Foreground threads keep the application alive for as long as any one of them is running, whereas background threads do not. Once all foreground threads finish, the application ends, and any background threads still running abruptly terminate.

Note : A thread’s foreground/background status has no relation to its priority or allocation of execution time.

If this program is called with no arguments, the worker thread assumes foreground status and will wait on the ReadLine statement for the user to press Enter. Meanwhile, the main thread exits, but the application keeps running because a foreground thread is still alive.

On the other hand, if an argument is passed to Main(), the worker is assigned background status, and the program exits almost immediately as the main thread ends (terminating the ReadLine).

When a process terminates in this manner, any finally blocks in the execution stack of background threads are circumvented. This is a problem if your program employs finally (or using) blocks to perform cleanup work such as releasing resources or deleting temporary files. To avoid this, you can explicitly wait out such background threads upon exiting an application. There are two ways to accomplish this:

  • If you’ve created the thread yourself, call Join on the thread.
  • If you’re on a pooled thread, use an event wait handle.

In either case, you should specify a timeout, so you can abandon a renegade thread should it refuse to finish for some reason. This is your backup exit strategy: in the end, you want your application to close — without the user having to enlist help from the Task Manager!

Permanent link to this article:

2 pings

  1. […] « Threading in C# Part -3 […]

  2. […] is in the continuation of previous five Posts: Threading in C# Part -1 Threading in C# Part -2 Threading in C# Part -3 Threading in C# Part -4 Threading in C# Part […]

Leave a Reply

Your email address will not be published.