The series so far:

  1. The Unconventional Guide to Introduction to Thread
  2. Why Do People Think Dedicated CLR Threads is a Good Idea?
  3. How Not Knowing Thread Members and Execution State Makes You a Rookie
  4. Doing Thread Scheduling and Priority the Right Way
  5. How to Start Using CLR's Thread Pool
  6. What Wikipedia Can't Tell You About Thread Execution Contexts
  7. The Insider's Guide to Cooperative Cancellation and Timeout

Execution States of a thread

ThreadState Enumeration

ThreadStateCycle

Thread State Cycle

Common States

The most common states as used in the block diagram:

  1. Unstarted:
    A thread is Created within the common language runtime but not Started still.
  2. Running:
    After a Thread calls Start method
  3. WaitSleepJoin:
    After a Thread calls its Wait or Sleep or Join method
  4. Suspended:
    Thread Responds to a Suspend method call.
  5. Stopped:
    The Thread is Stopped, either normally or Aborted.

Other States

Apart from these states that we have already seen in the block diagram, other states are:

  1. SuspendRequested:
    The thread is being requested to suspend.
  2. AbortRequested:
    After the Thread.Abort method invokes on the thread, but the thread has not yet received the pending ThreadAbortException that attempts to terminate it.
  3. Aborted:
    The thread state includes AbortRequested and the thread is now dead, but its state has not yet changed to Stopped.
  4. StopRequested:
    This state is for internal use only, when thread blocks on a call to Wait, and another thread calls Abort on the blocked thread, the blocked thread state is in both the WaitSleepJoin and the AbortRequested states at the same time. In this case, as soon as the thread returns from the call to Wait or is interrupted, it receives the ThreadAbortException to begin aborting.

Members of Thread Class

Thread class creates and controls a thread, sets its priority, and gets its status.

  • ThreadState:
    Specifies the execution states of a Thread.
  • Thread (ThreadStart):
    Initializes a new instance of the Thread class and ThreadStart represents the method that executes on a Thread.
  • void Start ()
    Causes the operating system to change the state of the current instance to ThreadState.Running, and optionally supplies an object containing data to be used by the method the thread executes.
  • static bool Yield()
    Causes the calling thread to yield execution to another thread that is ready to run on the current processor. (the operating system selects the thread to yield to)
  • Name
    Gets or sets the name of the thread
  • IsAlive
    Gets a value indicating the execution status of the current thread
  • IsBackground
    Gets or sets a value indicating whether or not a thread is a background thread. We cover in detail in the next topic.
  • static CurrentThread
    Retrieve a reference to the currently executing thread from the code that the thread is executing.
  • static Sleep (int millisecondsTimeout)
    Suspends the current thread for the specified number of milliseconds
  • static Sleep(TimeSpan time)
    Suspends the current thread for the specified amount of time.
  • public void Interrupt()
    Interrupts a thread that is in the WaitSleepJoin thread state. If this thread is not currently blocked in a wait, sleep, or join state, it will be interrupted when it next begins to block. This method causes the ThreadInterruptedException to be thrown when the thread is blocked.
  • public void Abort()
    Called on an thread that is under execution which causes the thread to stop and throws ThreadAbortException.

Important Notes on ThreadAbortException and ResetAbort

  1. The thread is not guaranteed to abort immediately, or at all. This situation may arise as a result of lot of computation in the finally blocks that are called as part of the abort when ThreadAbortException is thrown.
  2. ThreadAbortException is a special exception that can be caught by application code, but is re-thrown at the end of the catch block unless ResetAbort is called. ResetAbort cancels the request to abort, and prevents the ThreadAbortException from terminating the thread.

Caution Note!
Do not use the Suspend and Resume methods to synchronise the activities of threads.

  1. You have no way of knowing what code a thread is executing when you suspend it.
  2. If you suspend a thread while it holds locks during a security permission evaluation, it might block other threads in the AppDomain.
  3. If you suspend a thread while it is executing a class constructor, other threads get blocked in the AppDomain that attempt to use that class. Deadlocks can occur very quickly.

DEMO

1. Passing Parameter to threads with ThreadStart

The ThreadStart delegate doesn't take any parameters. We generally create a new instance of a class and use its instance to store the information we need to pass down.

// Define a class to store information
public class FileParser
{
    string path;

    public FileParser (string path)
    {
        this.path = path;
    }

    public void Parse()
    {
        // use file path here
    }
}
// Execute Parse method using Thread
FileParser parser = new FileParser (myFile);
new Thread (new ThreadStart (parser.Parse)).Start();

2. Passing Parameters to threads with ParameterizedThreadStart

The ParameterizedThreadStart delegate takes object as a parameter and returns void.

// Method defined to be executed by the thread
static void Parse(object path)
{
    // use file path here by casting it before usage
}
// Pass the argument in overloaded Thread.Start method
Thread t = new Thread (new ParameterizedThreadStart(Parse));
t.Start (myFile);

3. Waiting on the thread until given thread terminates Join

The below example show a classic example of Data Race condition, present in the code. Here the count variable is shared by the Main Thread and the counterWork Thread.

Thread.Join pauses the main thread and waits for completion of the other threads.

Can you guess the output?
Is it 10? hmmm...

using System;
using System.Threading;

public class Test
{
    static int count=0;
    
    static void Main()
    {
        // Start the counter
        ThreadStart counterWork = new ThreadStart(IncreementCounter);
        Thread counterThread = new Thread(counterWork);
        counterThread.Start();
        
        for (int i=0; i < 5; i++)
        {
            count++;
        }
        
        // Waits for the counter thread to complete
        counterThread.Join();
        Console.WriteLine ("Final count: {0}", count);
    }
    
    static void IncreementCounter()
    {
        for (int i=0; i < 5; i++)
        {
            count++;
        }
    }
}    

The output seems to be straightforward, 10 feels right everytime. It's not guaranteed to be 10.

  1. Increment Condition:
    Both Threads might read the old value for count variable, thus making it out of date.
    Three steps for increment condition.
    1. Read the value
    2. Increment the value
    3. Update the value
  2. Atomicity
    Above scenario violates Atomicity. Atomicity states the operations as atomic,
    1. Atomic write: you can't have another thread reading the value halfway through the write.
    2. Atomic read: you can't have another thread changing the value halfway through the read.