Threading in .NET guide – Part 1
Hello my dear friend!
I'm starting the collection of articles about threading|multithreading programming in C# .NET.
Why now? I'm really wanted to write such collection year ago but as usually work is on the first place. I've got new experience in programming and ready to give materials for your brain in easiest way. So, please, be patient and I'll give samples to be understandable for beginners and for advanced.
Let's get the party!
Part 1 devoted to the introduction in threading.
I have read a lot of material dedicated to threading in .NET and finally want to share my knowledge and with the same way to learn something new. Next part I plan to give you after new year and can't say for sure it's going to be about. Probably it's going to be about Lifecycle of Threads or Synchronization. Just follow me on Tweeter and you'll be in touch with all updates!
Understanding Threading
- Threads are basically light weight processes responsible for multitasking within a single application.
- The base class used for threading is
System.Threading
. - Threads are managed under the Common Language Runtime, programmers don't have to manage any threads explicitly.
- Threads are implemented when you have situations in which you want to perform more then one task at a time.
- In case of synchronization, since you have limited amount of recourses, you may have to restrict the access to the resources to one thread at a time. In these situations, you may implement locking on the threading to over come the scenarios.
- An apartment is a logical container within a process and is used for objects that share the same thread-access requirement. Objects in the apartment can all receive method calls from any object in any thread in the apartment. And managed objects (object created within CLR) are responsible for thread safety.
- Threads can be used under situations where we must wait for an event such as user input, a read from file, or receipt of data over the network.
Understanding Process and Thread
Each process provides the resources needed to execute a program. A process has a virtual address space, executable code, open handles to system objects, a security context, a unique process identifier, environment variables, a priority class, minimum and maximum working set sizes, and at least one thread of execution. Each process is started with a single thread, often called the primary thread, but can create additional threads from any of its threads.
A thread is the entity within a process that can be scheduled for execution. All threads of a process share its virtual address space and system resources. In addition, each thread maintains exception handlers, a scheduling priority, thread local storage, a unique thread identifier, and a set of structures the system will use to save the thread context until it is scheduled. The thread context includes the thread's set of machine registers, the kernel stack, a thread environment block, and a user stack in the address space of the thread's process. Threads can also have their own security context, which can be used for impersonating clients.
Concept and basic sample
A C# client program starts in a single thread created automatically by the CLR and operating system, and is made multithreaded by creating additional threads. Here’s a simple example and its output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class ThreadSample { static void Main() { // Create new instance of a thread Thread simpleThread = new Thread (WriteA); //Start our thread simpleThread.Start(); // Simultaneously, we are going tu run WriteB() function on a main thread WriteB(); } static void WriteA() { for (int i = 0; i < 500; i++) Console.Write ("A"); } static void WriteB() { for (int i = 0; i < 500; i++) Console.Write ("B"); } } |
Notice: All threaded applications assume the following namespaces are imported:
1 2 |
using System; using System.Threading; |
The main thread creates a new thread simpleThread
on which it runs a method that repeatedly prints the character “A”. Simultaneously, the main thread repeatedly prints the character “B”:
Once started, a thread’s IsAlive
property returns true
, until the point where the thread ends. A thread ends when the delegate passed to the Thread
’s constructor finishes executing. Once ended, a thread cannot restart. The CLR assigns each thread its own memory stack so that local variables are kept separate.
Working with Threads
As we saw in basic sample, 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:
1 |
public delegate void ThreadStart(); |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class ThreadSample { static void Main() { Thread t = new Thread (new ThreadStart (Go)); t.Start(); // Run Go() on the new thread. Go(); // Simultaneously run Go() in the main thread. } static void Go() { Console.WriteLine ("hello!"); } } |
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:
1 |
Thread t = new Thread (Go); // No need to explicitly use ThreadStart |
Another shortcut is to use a lambda expression or anonymous method:
1 2 3 4 5 |
static void Main() { Thread t = new Thread ( () => Console.WriteLine ("Hello!") ); t.Start(); } |
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:
1 2 3 4 5 6 7 8 9 10 |
static void Main() { Thread t = new Thread ( () => Print ("Hello from thread!")); t.Start(); } static void Print (string message) { Console.WriteLine (message); } |
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:
1 2 3 4 5 |
new Thread (() => { Console.WriteLine ("I'm running on another thread!"); Console.WriteLine ("And it's so easy!"); }).Start(); |
You can do the same thing almost as easily in C# 2.0 with anonymous methods:
1 2 3 4 |
new Thread (delegate() { ... // Do some staff }).Start(); |
Another technique is to pass an argument into Thread
’s Start
method:
1 2 3 4 5 6 7 8 9 10 11 |
static void Main() { Thread t = new Thread (Print); t.Start ("Hello from thread!"); } static void Print (object messageObj) { string message = (string) messageObj; // We need to cast here Console.WriteLine (message); } |
This works because Thread
’s constructor is overloaded to accept either of two delegates:
1 2 |
public delegate void ThreadStart(); public delegate void ParameterizedThreadStart (object obj); |
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:
1 2 |
for (int i = 0; i < 10; i++) new Thread (() => Console.Write (i)).Start(); |
The output is nondeterministic! Here’s a typical result:
1 |
0223557799 |
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:
1 2 3 4 5 |
for (int i = 0; i < 10; i++) { int temp = i; new Thread (() => Console.Write (temp)).Start(); } |
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:
1 2 3 4 5 6 7 8 |
string text = "t1"; Thread t1 = new Thread ( () => Console.WriteLine (text) ); text = "t2"; Thread t2 = new Thread ( () => Console.WriteLine (text) ); t1.Start(); t2.Start(); |
Because both lambda expressions capture the same text
variable, t2
is printed twice:
1 2 |
t2 t2 |
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.
A thread’s foreground/background status has no relation to its priority or allocation of execution time.
You can query or change a thread’s background status using its IsBackground
property. Here’s an example:
1 2 3 4 5 6 7 8 9 |
class PriorityTest { static void Main (string[] args) { Thread worker = new Thread ( () => Console.ReadLine() ); if (args.Length > 0) worker.IsBackground = true; worker.Start(); } } |
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!
If a user uses the Task Manager to forcibly end a .NET process, all threads “drop dead” as though they were background threads. This is observed rather than documented behavior, and it could vary depending on the CLR and operating system version.
Foreground threads don’t require this treatment, but you must take care to avoid bugs that could cause the thread not to end. A common cause for applications failing to exit properly is the presence of active foreground threads.
Thread Priority
A thread’s Priority
property determines how much execution time it gets relative to other active threads in the operating system, on the following scale:
1 |
enum ThreadPriority { Lowest, BelowNormal, Normal, AboveNormal, Highest } |
This becomes relevant only when multiple threads are simultaneously active.
Think carefully before elevating a thread’s priority — it can lead to problems such as resource starvation for other threads.
Elevating a thread’s priority doesn’t make it capable of performing real-time work, because it’s still throttled by the application’s process priority. To perform real-time work, you must also elevate the process priority using the Process
class in System.Diagnostics
(we didn’t tell you how to do this):
1 2 |
using (Process p = Process.GetCurrentProcess()) p.PriorityClass = ProcessPriorityClass.High; |
ProcessPriorityClass.High
is actually one notch short of the highest priority: Realtime
. Setting a process priority to Realtime
instructs the OS that you never want the process to yield CPU time to another process. If your program enters an accidental infinite loop, you might find even the operating system locked out, with nothing short of the power button left to rescue you! For this reason, High
is usually the best choice for real-time applications.
If your real-time application has a user interface, elevating the process priority gives screen updates excessive CPU time, slowing down the entire computer (particularly if the UI is complex). Lowering the main thread’s priority in conjunction with raising the process’s priority ensures that the real-time thread doesn’t get preempted by screen redraws, but doesn’t solve the problem of starving other applications of CPU time, because the operating system will still allocate disproportionate resources to the process as a whole. An ideal solution is to have the real-time worker and user interface run as separate applications with different process priorities, communicating via Remoting or memory-mapped files. Memory-mapped files are ideally suited to this task.
Even with an elevated process priority, there’s a limit to the suitability of the managed environment in handling hard real-time requirements. In addition to the issues of latency introduced by automatic garbage collection, the operating system may present additional challenges — even for unmanaged applications — that are best solved with dedicated hardware or a specialized real-time platform.
Exception Handling
Any try
/catch
/finally
blocks in scope when a thread is created are of no relevance to the thread when it starts executing. Consider the following program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public static void Main() { try { new Thread (Go).Start(); } catch (Exception ex) { // We'll never get here! Console.WriteLine ("Exception!"); } } static void Go() { throw null; } // Throws a NullReferenceException |
The try
/catch
statement in this example is ineffective, and the newly created thread will be encumbered with an unhandled NullReferenceException
. This behavior makes sense when you consider that each thread has an independent execution path.
The remedy is to move the exception handler into the Go
method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public static void Main() { new Thread (Go).Start(); } static void Go() { try { // ... throw null; // The NullReferenceException will get caught below // ... } catch (Exception ex) { // Typically log the exception, and/or signal another thread // that we've come unstuck // ... } } |
You need an exception handler on all thread entry methods in production applications — just as you do (usually at a higher level, in the execution stack) on your main thread. An unhandled exception causes the whole application to shut down. With an ugly dialog!
In writing such exception handling blocks, rarely would you ignore the error: typically, you’d log the details of the exception, and then perhaps display a dialog allowing the user to automatically submit those details to your web server. You then might shut down the application — because it’s possible that the error corrupted the program’s state. However, the cost of doing so is that the user will lose his recent work — open documents, for instance.
The “global” exception handling events for WPF and Windows Forms applications (Application.DispatcherUnhandledException
and Application.ThreadException
) fire only for exceptions thrown on the main UI thread. You still must handle exceptions on worker threads manually.
AppDomain.CurrentDomain.UnhandledException
fires on any unhandled exception, but provides no means of preventing the application from shutting down afterward.
There are, however, some cases where you don’t need to handle exceptions on a worker thread, because the .NET Framework does it for you. These are covered in upcoming sections, and are:
- Asynchronous delegates
BackgroundWorker
- The Task Parallel Library (conditions apply)
PriorityLevels of Thread
Any programmer can decide a priority for their thread, but ultimately, it's up to the recipient to decide what should be acted upon now and what can wait.
Windows uses a priority system from 0-31 where 31 is the highest. Anything higher than 15 needs to be done via an Administrator. Threads that have priority between 16-31 are considered real time, and will pre-empt lower priority level threads. Think about drivers/input devices and things like them; these will be running with priorities between 16-31.
And we have options when we use code, as the System.Threading.Thread
class exposes a Priority
property. If we look at what MSDN states, we could set on of the following priority levels of thread is set or checked by using an enumeration i.e. ThreadPriority. The valid values are for this enumeration are;
- Highest
- AboveNormal
- Normal
- BelowNormal
- Lowest
Note: Operating Systems are not required to honor the priority of a thread. Be careful what you do when setting priorities for threads.
End of part 1
Looks like this the main information I wanted to say about threads. Threading is a complex subject, and as such, this series will be quite hard, but I think worth a read.
Thanks for your patient! Hope this helpful! Feel free to leave a comment!
Leave a comment
You must be logged in to post a comment.