This article is a follow-up of my previous post on asynchronous programming in C#.

With the powerful abstractions provided by the .NET framework and C#, I rarely need to manipulate threads directly. Still, it's good to have a basic understanding of how threads work. Here's a cursory overview of threading support in C#.

Operating system provides unmanaged APIs to create and manage threads. .NET CLR (Common Language Runtime) abstracts and exposes them in System.Threading.Thread class. Each instance of the Thread class represents a 'point of control', or an independent worker that follows the instructions separately from other workers.

When you create an instance of a Thread, it needs to know what it should do, or what actions it should take. In C#, this abstraction of a unit of work is provided by a delegate ThreadStart, which does not take any parameters and returns nothing. 

Constructor of Thread takes this delegate that refers to the code that is to be executed. Once we have a thread that knows what it's going to do, we invoke it by calling its Start() method. This tells the operating system to start the concurrent execution of this new thread, and then the control immediately returns and executes the block that prints '/' to the console. 

public void Run()
{
    // If you want to pass an object to the delegate, 
    // use ParameterizedThreadStart class
    ThreadStart action = DoWork; 
    
    Thread runner = new Thread(action);
    
    runner.Start();

    for (int i = 0; i < 50; i++)
    {
        Console.Write("/");
    }
}

private void DoWork()
{
    for (int i = 0; i < 50; i++)
    {
        Console.Write(".");
    }
}
The above code prints output similar to this, which is different every time you run the program. 

///////////////////////////................................................../////////////////////// 
That means both blocks are run in parallel, and not in a sequence.

We can also create an instance of Thread by directly passing a method group. For example,

Thread runner = new Thread(DoWork);
Having an excessive number of threads running in your program have a negative impact on the performance. They are expensive and context switching is not free. To overcome this, .Net Base Class Library provides the abstraction of thread pool. 

Using thread pool, you don't create new threads yourself, but ask the pool to get a new one. When the work is finished, you return the thread to the pool. Basically, the pool manages the thread life-cycle, and you just borrow threads for doing your work.