Summary: in this tutorial, you’ll learn how to use the C# Thread
class to develop multi-threaded programs.
Introduction to the C# Thread class
We’ll start by creating a simple program:
using System.Diagnostics;
using static System.Console;
static void DoWork()
{
WriteLine("Doing the work...");
Thread.Sleep(1000);
WriteLine("done");
}
var watch = Stopwatch.StartNew();
DoWork();
DoWork();
watch.Stop();
WriteLine($"It took {watch.Elapsed.Seconds} second(s) to complete.");
Code language: C# (cs)
Output:
Doing the work...
done
Doing the work...
done
It took 2 second(s) to complete.
Code language: C# (cs)
How it works.
First, define the DoWork()
method that takes one second to run. The DoWork()
method uses the Sleep()
static method of the Thread
class to delay for one second.
Second, use the Stopwatch
object to measure the running time of the program. The Startnew()
method of the Stopwatch
class initializes a Stopwatch
object, resets the elapsed time to zero, and starts measuring the time.
Third, call the DoWork()
method twice.
Finally, stop measuring the elapsed time and print out the time that it took to run.
Because each DoWork()
method takes one second to complete, the whole program takes about 2 seconds which is as expected.
Using the C# Thread class
To make the above program multi-threaded, you use the Thread
class. The steps for using the Thread
class are as follows:
First, create a new instance of the Thread
class and pass a method to its constructor:
var t = new Thread(method);
Code language: C# (cs)
Second, call the Start()
method to start the execution of the thread:
t.Start()
Code language: C# (cs)
Third, call the Join()
method to wait for the thread to complete:
t.Join()
Code language: C# (cs)
The following example shows how to use the Thread
class to create a multithreaded program:
using System.Diagnostics;
using static System.Console;
static void DoWork()
{
WriteLine("Doing the work...");
Thread.Sleep(1000);
WriteLine("done");
}
var watch = Stopwatch.StartNew();
var t1 = new Thread(DoWork);
var t2 = new Thread(DoWork);
// start both threads
t1.Start();
t2.Start();
// wait for both threads completed
t1.Join();
t2.Join();
watch.Stop();
WriteLine($"It took {watch.Elapsed.Seconds} second(s) to complete.");
Code language: C# (cs)
Output:
Doing the work...
Doing the work...
done
done
It took 1 second(s) to complete.
Code language: C# (cs)
In this example, the program took only one second to complete even though it calls the DoWork()
method twice. In other words, its speed is double in comparison with the program that uses a single thread.
Passing arguments to the threads
To pass arguments to threads, you can:
- First, define a parameter with the type object in the method that will be executed by the thread.
- Second, pass the argument to the
Start()
method.
For example:
using System.Diagnostics;
using static System.Console;
static void DoWork(object? arg)
{
if (arg == null)
{
return;
}
string message = (string)arg;
Thread.Sleep(1000);
WriteLine(message);
}
var watch = Stopwatch.StartNew();
var t1 = new Thread(DoWork);
var t2 = new Thread(DoWork);
// start both threads
t1.Start("Hi");
t2.Start("Bye");
// wait for both threads completed
t1.Join();
t2.Join();
watch.Stop();
WriteLine($"It took {watch.Elapsed.Seconds} second(s) to complete.");
Code language: C# (cs)
Output:
Hi
Bye
It took 1 second(s) to complete.
Code language: C# (cs)
Alternatively, you can pass a lambda expression with arguments to the Thread
constructor like this:
using System.Diagnostics;
using static System.Console;
static void DoWork(string message)
{
Thread.Sleep(1000);
WriteLine(message);
}
var watch = Stopwatch.StartNew();
var t1 = new Thread(() => DoWork("Hi"));
var t2 = new Thread(() => DoWork("Bye"));
// start both threads
t1.Start();
t2.Start();
// wait for both threads completed
t1.Join();
t2.Join();
watch.Stop();
WriteLine($"It took {watch.Elapsed.Seconds} second(s) to complete.");
Code language: C# (cs)
Output:
Hi
Bye
It took 1 second(s) to complete.
Code language: C# (cs)
Using a lambda expression is a preferred way of passing arguments to the thread since the method doesn’t need to cast the object to the correct type before processing it.
Practical uses of C# multithreading
The following program checks a list of websites and displays their corresponding HTTP status codes:
using System.Diagnostics;
using static System.Console;
static void CheckHttpStatus(string url)
{
HttpClient client = new();
var response = client.GetAsync(url).Result;
WriteLine($"The HTTP status code of {url} is {response.StatusCode}");
}
List<string> urls = new(){
"https://www.google.com/",
"https://www.duckduckgo.com/",
"https://www.yahoo.com/",
};
var watch = Stopwatch.StartNew();
urls.ForEach(url => CheckHttpStatus(url));
watch.Stop();
WriteLine($"It took {watch.Elapsed.Seconds} second(s) to complete.");
Code language: C# (cs)
Output:
The HTTP status code of https://www.google.com/ is OK
The HTTP status code of https://www.duckduckgo.com/ is OK
The HTTP status code of https://www.yahoo.com/ is OK
It took 3 second(s) to complete.
Code language: C# (cs)
How it works.
First, define a static method CheckHttpStatus()
that uses the HttpClient
object to get the HTTP status code of a URL and display its result:
static void CheckHttpStatus(string url)
{
HttpClient client = new();
var response = client.GetAsync(url).Result;
WriteLine($"The HTTP status code of {url} is {response.StatusCode}");
}
Code language: C# (cs)
Second, define a list of URLs
to check:
List<string> urls = new(){
"https://www.google.com/",
"https://www.duckduckgo.com/",
"https://www.yahoo.com/",
};
Code language: C# (cs)
Third, pass each entry of the URLs
list to the CheckHttpStatus()
method. Also, wrap the method calls inside a block that uses the Stopwatch
to measure the execution time:
var watch = Stopwatch.StartNew();
urls.ForEach(url => CheckHttpStatus(url));
watch.Stop();
WriteLine($"It took {watch.Elapsed.Seconds} second(s) to complete.");
Code language: C# (cs)
Since the program deals with network requests. It’s ideal for utilizing multithreading to improve performance.
The following program displays the HTTP status code of a list of URLs
but uses multithreading instead:
using System;
using System.Diagnostics;
using static System.Console;
static void CheckHttpStatus(string url)
{
HttpClient client = new();
var response = client.GetAsync(url).Result;
WriteLine($"The HTTP status code of {url} is {response.StatusCode}");
}
List<string> urls = new(){
"https://www.google.com/",
"https://www.duckduckgo.com/",
"https://www.yahoo.com/",
};
List<Thread> threads = new();
urls.ForEach(url => threads.Add(new Thread(() => CheckHttpStatus(url))));
var watch = Stopwatch.StartNew();
// start the threads
threads.ForEach(thread=> thread.Start());
// wait for all threads to complete
threads.ForEach(thread => thread.Join());
watch.Stop();
WriteLine($"It took {watch.Elapsed.Seconds} second(s) to complete.");
Code language: C# (cs)
Output:
The HTTP status code of https://www.google.com/ is OK
The HTTP status code of https://www.duckduckgo.com/ is OK
The HTTP status code of https://www.yahoo.com/ is OK
It took 2 second(s) to complete.
Code language: C# (cs)
Summary
- Use the
Thread
class to create a new thread. - Call the
Start()
method to start executing the thread. - Call the
Join()
method to wait for the thread to complete. - Use lambda expressions to pass arguments to threads.