Threading Concepts in .NET — Part 2

Dino Buljubasic
6 min readFeb 19, 2018

--

This is second part of my post about threads in .NET. To see first part, check Threading in .NET — Part 1.

Thread Class

Thread class allows you to create and manage threads. You can set threads priority and query or change their status. In next example, we’ll use Thread class to run a method on separate thread. The method does not receive nor return any parameters.

Thread Class

Output:

Non-parameterized thread example00000000000000011110000000000111111

The comments in the example above are self-explanatory. The important parts worth mentioning are the calls to Thread.Sleep and Thread.Join methods.

Thread.Sleep(0) will signal OS that the current thread has finished its work and that there is no need to wait until the end of its CPU time-slice. Instead, OS will immediately switch to another thread.

Thread.Join is called to yield a thread. In example above, it is called on main thread. That means, main thread will pause to wait until another thread finishes.

Thread Priorities

Threads are by default created with Normal priority but you can change the priority to low or high although that is normally not recommended. Low priority threads are used for applications with lower priority which do not and should not compete with other applications for CPU time. One such application would be your screensaver for example. Making a thread a higher priority may lead to other threads being starved. So be careful and avoid changing priorities unless you are absolute positive you know what you are doing.

Foreground and Background threads

Important to know about threads is also that they can be foreground or background threads.

Foreground threads are all threads created explicitly by using new Thread(). Foreground threads keep the application alive as long as any of foreground threads are running. Once all are done, application ends. Threads being part of managed thread pool (their IsThreadpoolThread is True) are background threads. Background threads will not keep an application alive. Once all foreground threads finish their work, the application ends and CLR will then terminate all background threads, too.

Foreground and Background Threads

As per example above, we create a new thread using new Thread(). This means the thread is created as foreground thread and it will therefore keep application alive until it is done doing its work in which case, CLR will end up your application (and terminate any of background threads if any).

However, if you uncomment the line t.IsBackground = true; above, the thread is not foreground thread but background thread. Therefore, CLR will terminate application immediately (and any of other background threads if any).

Parameterized Threads

Until now, we used Thread constructor that did not take any parameters in new ThreadStart() (t.Start). However, we may need to pass a value to our thread in which case we can use ParameterizedThreadStart() constructor which allows us to pass some data into the start method and use it in our method that is executing on another thread.

Parameterized Threads

In the example above, we are using ParameterizedThreadStart to pass an integer into method that will be executed on another (background) thread. The integer is passed in as an object, so it must be downcasted back to int before it can be used as shown in ThreadMethod.

Stopping Threads

To stop a thread, you can use Thread.Abort method. When called, a ThreadAbort exception is thrown on target thread. However, Thread.Abort is executed by another thread (i.e. main thread). This means that it can be executed at any time which in turn can lead to corrupt state of your application. Therefore, it is better to use a flag and stop a thread based on the status of that flag rather than using Thread.Abort.

Stopping Threads

Above example uses stopped boolean flag. When you run this example, thread t is created and started first. Then console will write the prompt message “Press any key to exit.” and stop to wait on user to enter a key (Console.ReadKey() line above). The thread will print its message (“Running….”) until user hits a key in which case, flag is set to true to mark execution of the thread as completed. The thread is still running and it could be doing something that needs to finish so we call Join to make main thread wait for it to finish before closing the application.

Output:

Press any key to exit.Running...Running...Running...Running...Running...Running...Running...

Thread Data

A thread maintains its own call stack and uses it to store any local variables to the thread as well as any methods it called. However, a thread can also have its own data that is not a local variable. To make a variable thread’s own, you can annotate a non-local (to thread) variable with ThreadStatic attribute. Each thread will then get its own copy of that variable.

Thread Data

Output (with ThreadStatic):

Main thread: counter=10Thread B: 1Thread B: 2Thread B: 3Thread B: 4Thread B: 5Thread B: 6Thread B: 7Thread B: 8Thread B: 9Thread B: 10Thread A: 1Thread A: 2Thread A: 3Thread A: 4Thread A: 5Thread A: 6Thread A: 7Thread A: 8Thread A: 9Thread A: 10

As you can see, both threads got their own copy of counter and increment it to 10 max. You can also see that main thread on which counter was declared got it initialized to 10 while others did not. Other threads got it initialized to 0 instead, then incremented it to 1 in first run.

Output (without ThreadStatic):

Main thread: counter=10Thread B: 12Thread B: 13Thread B: 14Thread B: 15Thread B: 16Thread B: 17Thread B: 18Thread B: 19Thread B: 20Thread B: 21Thread A: 11Thread A: 22Thread A: 23Thread A: 24Thread A: 25Thread A: 26Thread A: 27Thread A: 28Thread A: 29Thread A: 30

However, without ThreadStatic, the variable was shared by each thread and therefore it was initialized to 10 but thread A then incremented it to 11, then thread B to 12 and so on.

So, we can prevent local variable from being shared among threads by using ThreadStatic attribute which then makes each thread get its own copy of the local variable.

Another way to set local thread data is by using ThreadLocal<T> class. This class takes a delegate to a method that initializes the value. But ThreadLocal can be used also on instance fields, not only static fields like in case of ThreadStatic attribute. In addition, ThreadLocal<T> class will initialize variable for all threads.

Setting local thread data using ThreadLocal

Output:

Press any key to exitThread A: counter=11Thread B: counter=11

As you can see, both threads got a copy of nonstatic variable counter initialized to 10, then incremented it to 11. Note that ThreadLocal class implements IDisposable interface; therefore, we should dispose of it once done using it to ensure no memory leak is introduced.

Thread Pools

When calling new Thread, you create a new thread which dies once it finishes its work. This can affect performance and cost some time and resources. Therefore, .NET has thread pools which when first created starts out as empty. But as work to be done is queued, thread pool will create new threads to deal with the work. As long as the work can be finished before more work comes in, no new threads will be created. If new threads are no longer used after some time, thread pool will kill them to reclaim resources.

So, once a thread from thread pool finishes a task, it will not die but it will be still available once needed. However, if it is not used for some time, thread pool will kill it. Threads in threadpool are all background threads. This means that a ThreadPool thread will not keep an application running after all foreground threads have exited.

Note that since thread pool reuses a thread, it does not clear the data in thread local storage (ThreadLocal<T>) or in fields that are marked with ThreadStatic attribute. Therefore, when a method examines thread local storage or fields that are marked with ThreadStatic attribute, the values it finds might be left over from an earlier use of the thread in thread pool.

--

--