ZetCode

C# Thread

last modified September 18, 2022

In this article we work with threads in C#.

A process is an instance of a program running in a computer. When a process starts, the common language runtime (CLR) automatically creates a single foreground thread to execute application code. A process can run multiple threads. A thread is a unique execution path of a program.

In C#, the Thread class represents a thread. It creates and controls a thread, sets its priority, and gets its status. The Thread class is part of the System.Threading namespace.

Both processes and threads are independent sequences of execution. The following table summarizes the differences between a process and a thread:

Process Thread
processes run in separate memory (process isolation) threads share memory
uses more memory uses less memory
children can become zombies no zombies possible
more overhead less overhead
slower to create and destroy faster to create and destroy
easier to code and debug can become harder to code and debug
Table: Process vs thread

C# Thread Start

The Start method starts a thread.

Program.cs
Console.WriteLine("main started");
var id = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine($"main id: {id}");

var t = new Thread(task);
t.Start();

Console.WriteLine("main finished");

void task()
{
    Console.WriteLine("thread started");
    var id = Thread.CurrentThread.ManagedThreadId;
    Console.WriteLine($"thread id: {id}");
}

A new thread is created and then launched with Start.

Console.WriteLine("main started");

The main program is itself a separate thread of execution.

var id = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine($"main id: {id}");

The Thread.CurrentThread.ManagedThreadId gets a unique identifier for the current managed thread.

var t = new Thread(task);

A new Thread is created. We pass a reference to the function that is executed in the thread.

t.Start();

The thread is started.

void task()
{
    Console.WriteLine("thread started");
    var id = Thread.CurrentThread.ManagedThreadId;
    Console.WriteLine($"thread id: {id}");
}

Inside the function, we get and print the thread id.

$ dotnet run
main started
main id: 1
main finished
thread started
thread id: 4

Notice that the main thread has finished before the other thread. The program waits for the thread to finish.

C# Thread pass arguments

In the next example, we show how to pass arguments to a thread.

Program.cs
int n = 5;
string word = "falcon";

// var t = new Thread(() => { for (int i = 0; i < n; i++) Console.WriteLine(word); });
var t = new Thread(() => repeat(n, word));
t.Start();

void repeat(int n, string word)
{
    for (int i = 0; i < n; i++)
    {
        Console.WriteLine(word);
    }
}

The thread prints a word n times. We pass the word and the number as arguments.

var t = new Thread(() => repeat(n, word));

The thread takes a lambda expression, where the repeat function is called with two parameters.

$ dotnet run
falcon
falcon
falcon
falcon
falcon

C# Thread.Sleep

The Tread.Sleep method suspends the current thread for the specified number of milliseconds. The method is useful for debugging and testing.

It is often used to simulate a long-running task.

Program.cs
for (int i = 0; i < 5; i++)
{
    var n = new Random().Next(500, 1500);

    var t = new Thread(() => task(n));
    t.Start();
}

void task(int n)
{
    var id = Thread.CurrentThread.ManagedThreadId;

    Console.WriteLine($"thread id: {id} started");
    Thread.Sleep(n);
    Console.WriteLine($"thread id: {id} finished in {n} ms");
}

In the program, we create five threads that sleep for a random number of milliseconds.

var n = new Random().Next(500, 1500);

We create a random number between 500 and 1500.

var t = new Thread(() => task(n));
t.Start();

We pass the random number to the newly created thread.

void task(int n)
{
    var id = Thread.CurrentThread.ManagedThreadId;

    Console.WriteLine($"thread id: {id} started");
    Thread.Sleep(n);
    Console.WriteLine($"thread id: {id} finished in {n} ms");
}

The function that is run within the thread suspends its execution with Tread.Sleep for n milliseconds.

$ dotnet run
thread id: 5 started
thread id: 6 started
thread id: 4 started
thread id: 7 started
thread id: 8 started
thread id: 5 finished in 822 ms
thread id: 8 finished in 891 ms
thread id: 6 finished in 902 ms
thread id: 4 finished in 946 ms
thread id: 7 finished in 1113 ms

C# foreground & background thread

There are two kings of threads: foreground and background. The background threads do not prevent a process from terminating. When all foreground threads belonging to a process have terminated, then the CLR ends the process.

A default thread is a foreground thread. We change a thread to a background thread with IsBackground property.

Program.cs
Console.WriteLine("started main");

for (var i = 0; i < 5; i++)
{
    var rn = new Random().Next(500, 1500);
    var t = new Thread(() => task(rn));
    t.IsBackground = true;
    t.Start();

}

void task(int n)
{
    Thread.Sleep(n);
    var id = Thread.CurrentThread.ManagedThreadId;
    Console.WriteLine($"{id} finished in {n} ms");
}

Console.WriteLine("finished main");

In the example, we create a main program thread and five background threads.

$ dotnet run
started main
finished main

Once the only foreground thread has finished, all other background threads are terminated and the program is finished. The background threads had no time to run.


$ dotnet run
started main
finished main
8 finished in 572 ms
4 finished in 770 ms
7 finished in 772 ms
6 finished in 1145 ms
5 finished in 1397 ms

When we commed the t.IsBackground = true; line, we create foreground threads. Then the main thread waits for other foreground threads to finish and the five threads are run.

C# Thread Join

The Join method blocks the calling thread until the specified thread terminates.

Program.cs
Console.WriteLine("main started");
var id = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine($"main id: {id}");

Thread[] threads = new Thread[5];

for (int i = 0; i < 5; i++)
{
    var n = new Random().Next(500, 1500);
    threads[i] =  new Thread(() => task(n));
}

foreach (var thread in threads)
{
    thread.Start();
}

foreach (var thread in threads)
{
    thread.Join();
}

void task(int n)
{
    var id = Thread.CurrentThread.ManagedThreadId;

    Console.WriteLine($"thread id: {id} started");
    Thread.Sleep(n);
    Console.WriteLine($"thread id: {id} finished in {n} ms");
}

Console.WriteLine("main finished");

In this program, the main thread waits for all other threads to finish.

Thread[] threads = new Thread[5];

for (int i = 0; i < 5; i++)
{
    var n = new Random().Next(500, 1500);
    threads[i] =  new Thread(() => task(n));
}

We create an array of five threads that will sleep for a random number of milliseconds.

foreach (var thread in threads)
{
    thread.Start();
}

First, we start all five threads.

foreach (var thread in threads)
{
    thread.Join();
}

With Join, we block the main thread until all the five threads in the array finish.

$ dotnet run
main started
main id: 1
thread id: 4 started
thread id: 5 started
thread id: 6 started
thread id: 7 started
thread id: 8 started
thread id: 7 finished in 802 ms
thread id: 4 finished in 1080 ms
thread id: 8 finished in 1354 ms
thread id: 6 finished in 1358 ms
thread id: 5 finished in 1461 ms
main finished

C# Thread with Stopwatch

With Stopwatch we can accurately measure elapsed time.

Program.cs
using System.Diagnostics;

Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

var sw = new Stopwatch();
sw.Start();

Thread[] threads = new Thread[10];

for (var i = 0; i < 10; i++)
{
    var t = new Thread(() =>
    {
        var id = Thread.CurrentThread.ManagedThreadId;
        var r = new Random().Next(500, 1500);
        Thread.Sleep(r);
        Console.WriteLine($"{id} finished in {r} ms");
    }
);
    threads[i] = t;
}

foreach (var t in threads)
{
    t.Start();
}

foreach (var t in threads)
{
    t.Join();
}

sw.Stop();
var elapsed = sw.ElapsedMilliseconds;

Console.WriteLine($"elapsed: {elapsed} ms");

We create ten threads that run a random number of milliseconds. The main thread waits until all other threads finish and calculate the elapsed time.

var sw = new Stopwatch();
sw.Start();

We create the Stopwatch and run it.

sw.Stop();
var elapsed = sw.ElapsedMilliseconds;

Console.WriteLine($"elapsed: {elapsed} ms");

At the end, we calculate the elapsed time and print results.

$ dotnet run
1
13 finished in 539 ms
4 finished in 547 ms
9 finished in 617 ms
6 finished in 782 ms
8 finished in 787 ms
7 finished in 917 ms
10 finished in 968 ms
12 finished in 1170 ms
5 finished in 1468 ms
11 finished in 1488 ms
elapsed: 1488 ms

The program runs as long as the longest thread.

In this article we have worked with threads in C#.

List all C# tutorials.