In this article we will study the .NET threading API, how to create threads in C#, start and stop them, define their priorities and states.
Applications written on the .NET platform are naturally threaded. Let us study threading with respect to C# as the programming language. The runtime environment starts execution of the program with the Main () method in one thread. We all know that there is an automatic garbage collection happening in the background. This takes place in another thread. All of this happens so naturally that it goes unnoticed. There are situations when we need to add threads to our application. We have already seen the numerous situations in Multithreading -- part1 of the article, where we might feel the need to add new threads. A few common requirements being when you need to handle user-input, or do a lengthy calculation etc. To put it more generally, any operation that is time-critical, or needs constant attention or is time-consuming should be placed in a thread of its own.
The classes and interfaces in the System.Threading namespace provide the multithreading support in the .NET platform. This namespace consists of a number of classes. We will be discussing the Thread class of this namespace.
System.Threading.Thread is the main class for creating threads and controlling them. The Thread class has a number of methods. A few interesting methods are shown below:
- Start(): starts the execution of the thread.
- Suspend(): suspends the thread, if the thread is already suspended, nothing happens.
- Resume() : resumes a thread that has been suspended.
- Interrupt(): interrupts a thread that is in the wait, sleep or join stage.
- Join(): blocks a calling thread until the thread terminates.
- Sleep(int x) : suspends the thread for specified amount of time (in milliseconds).
- Abort(): Begins the process of terminating the thread. Once the thread terminates, it cannot be restarted by calling the function Start() again.
You can pause/block a thread by calling Thread.Sleep or Thread.Suspend or Thread.Join. Calling the method Sleep() or Suspend() on a thread means, the thread does not get any processor time. There is a difference between these two ways of pausing a thread. Thread.Sleep causes a thread to stop immediately but the common language runtime waits until the thread has reached some safe point before calling the Suspend() method on the thread. One thread cannot call Sleep() on another thread but one thread can call Suspend() on the other thread and it causes the other thread to pause. Calling Resume() on the suspended thread breaks the thread out of the suspended state and allows it to continue execution. A single call to Resume() is sufficient to activate a thread regardless of the number of times Suspend() was called to block it. A thread that has already been terminated or has not yet started functioning cannot be suspended. Thread.Sleep(int.Infinite) causes a thread to sleep indefinitely. The thread can only wake up when it is interrupted by another thread that calls Thread.Interrupt or is aborted by Thread.Abort. You can use Thread.Interrupt to break a thread out of its blocking state but it throws a ThreadInterupptedException. You can either catch the exception, do whatever you want to do with the thread, or ignore the exception and let the run-time stop the thread. For managed wait, Thread.Interrupt and Thread.Abort both wake up the thread immediately.
It may be desirable at times to terminate a thread from some other thread. In such situations you use the Thread.Abort method to stop a thread permanently and using this function throws a ThreadAbortException. The terminating thread can catch the exception but it is difficult to suppress it. The only way that it can be suppressed is by calling Thread.ResetAbort method but it can only be called if this thread had been the one that had provoked the exception. Since, Thread.Abort is normally called by some thread A on some other thread B, B therefore, cannot invoke the method Thread.ResetAbort to suppress it from terminating. The Thread.Abort method lets the system quietly stop the thread without informing the user. Once aborted, a thread cannot be restarted. As this method does not say that the thread will abort immediately, hence to be sure that the thread has terminated, you can call Thread.Join to wait on the thread. Join is a blocking call that does not return until the thread has actually stopped executing. But remember, a thread may call Thread.Interrupt to interrupt another thread that is waiting on a call to Thread.Join.
As far as possible, you should avoid using Suspend() to block a thread as it could lead to serious problems like deadlocks. Imagine, what would happen if we suspend a thread that holds a resource that another thread would need. Rather, you should give different priorities to the thread based on their importance. You should, as far as possible, use Thread.Priority rather than Thread.Suspend.
This class also has a number of interesting properties as shown below:
- IsAlive: (if true, signifies that thread has been started and has not yet been terminated or aborted)
- Name (gets/sets the name of the thread)
- Priority (gets/sets the scheduling priority of a thread)
- ThreadState (gets a value containing the state of the current thread).
The example, shown below, is a very simple one that discusses how to apply these thread properties. More useful examples would follow in the coming series.
To create a thread, we need to instantiate the Thread class, passing a ThreadStart delegate (System.Threading.ThreadStart) in its constructor. This delegate contains the method where the thread will begin execution, when started. The Start() method of the Thread class then starts the execution of a new thread. Let us understand the concept with a small example.
using
System;
using System.Threading ;
namespace LearnThreads
{
class Thread_App
{
public static void First_Thread()
{
Console.WriteLine("First thread created");
Thread current_thread = Thread.CurrentThread;
string thread_details = "Thread Name: " + current_thread.Name +
"\r\nThread State: " + current_thread.ThreadState.ToString()+
"\r\n Thread Priority level:"+current_thread.Priority.ToString();
Console.WriteLine("The details of the thread are :"+ thread_details);
Console.WriteLine ("first thread terminated");
}
public static void Main()
{
ThreadStart thr_start_func = new ThreadStart (First_Thread);
Console.WriteLine ("Creating the first thread ");
Thread fThread = new Thread (thr_start_func);
fThread.Name = "first_thread";
fThread.Start (); //starting the thread
}
}
}
In this example, we are creating a new thread called fThread, which when started executes the function called First_Thread(). Note the use of the delegate called ThreadStart that contains the address of the function that needs to be executed when the thread's Start() is called.
Thread States
System.Threading.Thread.ThreadState property defines the state in which a thread is during the execution of the thread. Once created, a thread is always in at least one of the states till it is terminated. When a thread is created it is in Unstarted state. Start() method of the Thread class changes this state to a Running state for the thread. The thread continues to be in this state till it goes into a sleep or is suspended or is aborted or the thread terminates naturally. When the thread is suspended is goes into the Suspended state till another thread resumes it, when it is back again to the Running state. On being aborted or terminated, the thread is stopped and the ThreadState is Stopped. Once stopped, a thread can never leave the Stopped state just as once started a thread can never return to the Unstarted state. There is another state called Background state, which indicates whether a thread is running in the foreground or background. A thread can be in multiple states at a given point in time. Say, a thread is blocked on a call to Sleep and another thread calls Abort on the blocked thread. What happens?? The thread will be in both WaitSleepJoin and the AbortRequested states at the same time. As soon as the thread returns from a call to Sleep or is interrupted, the thread receives a ThreadAbortException to begin aborting.
Thread Priority
System.Threading.Thread.Priority enumeration defines the priority states of a thread that in turn determines how much processor time a thread gets to execute. Threads are executed based on their priority levels. High priority threads always get more processor time than their counterparts. If there is more than one thread at a high priority then the operating system cycles through the threads giving each high priority thread some amount of processor time. Lower priority threads do not get to execute as long as a higher priority thread is available. When there are no more threads of higher priority, the operating system then selects the next lower priority threads for execution. If at any point it encounters a higher priority thread, then the lower priority threads are preempted and give way to the higher priority thread to execute. Any new thread gets created with a Normal priority. You can then change the priority value to any of the values shown below (these are defined in the Priority property of the Thread class):
- Highest
- AboveNormal
- Normal
- BelowNormal
- Lowest
Conclusion:
This part was just the beginning to understand how threads are created and what properties they have. The System.Threading namespace has a lot many interesting features like locking a thread object, interprocess synchronization, classes that manage a groups of threads, or deadlock resolution. We would study these advanced features in the coming series.