This post is a part of preparation for 70-483 exam. Information written down here refers to the part Manage program flow.
.NET framework provides several mechanisms to write concurrency, parallel and asynchronous code. They all are included in System.Threading namespace. This namespace contains types that allow creating multithreaded applications. Today’s post is about the Thread class.
This topic is not required for the exam. However, the exam preparation materials sometimes references to it and I decided to write it down.
The Thread class is located in the System.Threading namespace and it has been a part of the .NET framework since version 1.1. It gives an ability to create new treads, manage their priority, get their status, tell Windows that the thread is long running, or configure other advanced options. In general using the Thread class you have control over all configuration options.
Basic usage of the Thread class
The code below is an example of an application using Thread class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
using System; using System.Threading; Thread t = new Thread(() => { Console.WriteLine("Additional thread started."); for(int i = 0; i< 5; i++) { Console.WriteLine("Additional thread is counting: {0}", i); Thread.Sleep(1000); } }); t.Start(); for(int i = 0; i< 7; i++) { Console.WriteLine("Main thread is counting: {0}", i); Thread.Sleep(500); } t.Join(); Console.WriteLine("Additional thread returned"); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//Main thread is counting: 0 //Additional thread started. //Additional thread is counting: 0 //Main thread is counting: 1 //Main thread is counting: 2 //Additional thread is counting: 1 //Main thread is counting: 3 //Main thread is counting: 4 //Additional thread is counting: 2 //Main thread is counting: 5 //Additional thread is counting: 3 //Main thread is counting: 6 //Additional thread is counting: 4 //Additional thread returned |
The output shows that those two threads run concurrently. I passed a lambda expression to the Thread class constructor but thread can be initialized within ThreadStart delegate as well.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
using System; using System.Threading; public static class Worker{ public static void Todo(){ Console.WriteLine("Additional thread started."); for(int i = 0; i< 2; i++) { Console.WriteLine("Additional thread is counting: {0}", i); Thread.Sleep(1000); } } } Thread t = new Thread(new ThreadStart(Worker.Todo)); t.Start(); for(int i = 0; i< 3; i++) { Console.WriteLine("Main thread is counting: {0}", i); Thread.Sleep(500); } t.Join(); Console.WriteLine("Additional threads returned."); |
Calling Start() method on a thread object runs the thread. This method has an overloaded version which takes object parameter and passes it to the thread. It’s useful when you want to pass additional data to the thread at start time.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
using System; using System.Threading; public static class Worker{ public static void Todo(object max){ Console.WriteLine("Additional thread started."); for(int i = 0; i< (int)max; i++) { Console.WriteLine("Additional thread is counting: {0}", i); Thread.Sleep(1000); } } } Thread t = new Thread((max) => { Console.WriteLine("Another additional thread started."); for(int i = 0; i< (int)max; i++) { Console.WriteLine("Another additional thread is counting: {0}", i); Thread.Sleep(1000); } }); Thread t2 = new Thread(new ParameterizedThreadStart(Worker.Todo)); t.Start(4); t2.Start(2); for(int i = 0; i< 3; i++) { Console.WriteLine("Main thread is counting: {0}", i); Thread.Sleep(500); } t.Join(); t2.Join(); Console.WriteLine("Additional threads returned."); |
At the bottom of each example you can notice Join() method call. This method blocks the calling thread to wait till the thread finishes execution.
Stopping the thread
Thread class contains an Abort method which raises a ThreadAbortException on the target thread. It is not the best choice because it can potentially leave a corrupt state and make your application unusable. Using a shared variable that both your target and your calling thread can access is an alternative to stop a thread. The example below shows that concept.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
using System; using System.Threading; bool started = true; Thread t = new Thread(() => { Console.WriteLine("Additional thread started."); int i; while(started) { Console.WriteLine("Additional thread is counting: {0}", i++); Thread.Sleep(1000); } }); t.Start(); for(int i = 0; i< 7; i++) { Console.WriteLine("Main thread is counting: {0}", i); Thread.Sleep(500); } started = false; t.Join(); Console.WriteLine("Additional thread returned"); |
ThreadStaticAttribute and ThreadLocal
Sometimes it’s reasonable to use static fields in the code. However, if the code is executed in multiple threads you can run into a problem where the field is influenced by each thread separately and it can lead you to unexpected troubles. Look at code above.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
using System; using System.Threading; static int sum; Thread t = new Thread(() => { Console.WriteLine("Additional thread started."); for(int i = 0; i < 3; i++) { Console.WriteLine("Additional thread is counting: {0}", i); sum += i; Thread.Sleep(1000); } Console.WriteLine("Additional thread result is: {0}", sum); }); t.Start(); for(int i = 0; i< 5; i++) { Console.WriteLine("Main thread is counting: {0}", i); Thread.Sleep(500); sum += i; } Console.WriteLine("Main thread result is: {0}", sum); t.Join(); Console.WriteLine("Additional thread returned"); |
1 2 3 4 5 6 7 8 9 10 11 12 |
//Main thread is counting: 0 //Additional thread started. //Additional thread is counting: 0 //Main thread is counting: 1 //Main thread is counting: 2 //Additional thread is counting: 1 //Main thread is counting: 3 //Additional thread is counting: 2 //Main thread is counting: 4 //Main thread result is: 13 //Additional thread result is: 13 //Additional thread returned |
The correct result should be 6 for the additional thread and 13 for the main thread. Instead both results are the same. Using ThreadStaticAttribute fixes this issue. This annotation gives each thread its unique copy of the field.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
using System; using System.Threading; [ThreadStatic] static int sum; Thread t = new Thread(() => { Console.WriteLine("Additional thread started."); for(int i = 0; i < 3; i++) { Console.WriteLine("Additional thread is counting: {0}", i); sum += i; Thread.Sleep(1000); } Console.WriteLine("Additional thread result is: {0}", sum); }); t.Start(); for(int i = 0; i< 5; i++) { Console.WriteLine("Main thread is counting: {0}", i); Thread.Sleep(500); sum += i; } Console.WriteLine("Main thread result is: {0}", sum); t.Join(); Console.WriteLine("Additional thread returned"); |
Unfortunately, when you want to initialize static field locally in each thread then ThreadStaticAttribute is not enough. Code like this:
1 2 |
[ThreadStatic] static int sum = 10; |
Will set sum field to 10 only for the current thread, the others will still have 0. ThreadLocal class deals with that problem. It takes a delegate as a parameter and initializes the value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
using System; using System.Threading; static ThreadLocal<int> sum = new ThreadLocal<int>(() => 10); Thread t = new Thread(() => { Console.WriteLine("Additional thread started."); for(int i = 0; i < 3; i++) { Console.WriteLine("Additional thread is counting: {0}", i); sum.Value += i; Thread.Sleep(1000); } Console.WriteLine("Additional thread result is: {0}", sum); }); t.Start(); for(int i = 0; i< 5; i++) { Console.WriteLine("Main thread is counting: {0}", i); Thread.Sleep(500); sum.Value += i; } Console.WriteLine("Main thread result is: {0}", sum); t.Join(); Console.WriteLine("Additional thread returned"); |
IsBackground property
One other thing worth to mention is that thread can be run either as a foreground or a background thread. There is only a slight difference, as MSDN portal says:
Background threads are identical to foreground threads with one exception: a background thread does not keep the managed execution environment running. Once all foreground threads have been stopped in a managed process (where the .exe file is a managed assembly), the system stops all background threads and shuts down.
Thread object has IsBackground property which defines whether the thread is a background or a foreground thread. The example below shows the usage of this property.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
using System; using System.Threading; Thread t = new Thread(() => { Console.WriteLine("Additional thread started."); for(int i = 0; i < 10; i++) { Console.WriteLine("Additional thread is counting: {0}", i); Thread.Sleep(1000); } }); t.IsBackground = true; t.Start(); for(int i = 0; i< 5; i++) { Console.WriteLine("Main thread is counting: {0}", i); Thread.Sleep(500); } Console.WriteLine("Program finished"); |
The difference is that the program finishes when the main thread ends its execution and thread t1 is terminated immediately. If the flag would be set to false (default value) then the thread t1 would count to 10.
Summary
That’s all for today. I hope the topic is covered well enough. In further posts I will focus on Thread Pool class and Task Parallel Library. Stay tuned.
Of course all above examples are available at github.
Pingback: [EN] Exam 70-483 Programming in C# - Introduction - Tymoteusz Kęstowicz .NET developer blog
very interesting I will try it if it’s really running thanks for sharing..
I would recommend using Task class instead. I will write about it if I find some time.