Creating and destroying threads
Creating and destroying threads are fundamental concepts of managed threading in .NET. We have already seen one code example that created a thread, but there are some additional constructors of the Thread
class that should be discussed first. Also, we will look at a few methods of pausing or interrupting thread execution. Finally, we will cover some ways to destroy or terminate a thread’s execution.
Let’s get started by going into more detail regarding creating and starting threads.
Creating managed threads
Creating managed threads in .NET is accomplished by instantiating a new Thread
object. The Thread
class has four constructor overloads:
Thread(ParameterizedThreadStart)
: This creates a newThread
object. It does this by passing a delegate with a constructor that takes an object as its parameter that can be passed when callingThread.Start()
.Thread(ThreadStart)
: This creates a newThread
object that will execute the method to be invoked, which is provided as theThreadStart
property.Thread(ParameterizedThreadStart, Int32):
This adds amaxStackSize
parameter. Avoid using this overload because it is best to allow .NET to manage the stack size.Thread(ThreadStart, Int32):
This adds amaxStackSize
parameter. Avoid using this overload because it is best to allow .NET to manage the stack size.
Our first example used the Thread(ThreadStart)
constructor. Let’s look at a version of that code that uses ParameterizedThreadStart
to pass a value by limiting the number of iterations of the while
loop:
Console.WriteLine("Hello, World!"); var bgThread = new Thread((object? data) => { if (data is null) return; int counter = 0; var result = int.TryParse(data.ToString(), out int maxCount); if (!result) return; while (counter < maxCount) { bool isNetworkUp = System.Net.NetworkInformation .NetworkInterface.GetIsNetworkAvailable(); Console.WriteLine($"Is network available? Answer: {isNetworkUp}"); Thread.Sleep(100); counter++; } }); bgThread.IsBackground = true; bgThread.Start(12); for (int i = 0; i < 10; i++) { Console.WriteLine("Main thread working..."); Task.Delay(500); } Console.WriteLine("Done"); Console.ReadKey();
If you run the application, it will run just like the last example, but the background thread should only output 12 lines to the console. You can try passing different integer values into the Start
method to see how that impacts the console output.
If you want to get a reference to the thread that is executing the current code, you can use the Thread.CurrentThread
static property:
var currentThread = System.Threading.Thread.CurrentThread;
This can be useful if your code needs to check the current thread’s ManagedThreadId
, Priority
, or whether it is running in the background.
Next, let’s look at how we can pause or interrupt the execution of a thread.
Pausing thread execution
Sometimes, it is necessary to pause the execution of a thread. A common real-life example of this is a retry mechanism on a background thread. If you have a method that sends log data to a network resource, but the network is unavailable, you can call Thread.Sleep
to wait for a specific interval before trying again. Thread.Sleep
is a static method that will block the current thread for the number of milliseconds specified. It is not possible to call Thread.Sleep
on a thread other than the current one.
We have already used Thread.Sleep
in the examples in this chapter, but let’s change the code slightly to see how it can impact the order of events. Change the Thread.Sleep
interval inside the thread to 10
, remove the code that makes it a background thread, and change the Task.Delay()
call to Thread.Sleep(100)
:
Console.WriteLine("Hello, World!"); var bgThread = new Thread((object? data) => { if (data is null) return; int counter = 0; var result = int.TryParse(data.ToString(), out int maxCount); if (!result) return; while (counter < maxCount) { bool isNetworkUp = System.Net.NetworkInformation. NetworkInterface.GetIsNetworkAvailable(); Console.WriteLine($"Is network available? Answer: {isNetworkUp}"); Thread.Sleep(10); counter++; } }); bgThread.Start(12); for (int i = 0; i < 12; i++) { Console.WriteLine("Main thread working..."); Thread.Sleep(100); } Console.WriteLine("Done"); Console.ReadKey();
When running the application again, you can see that putting a greater delay on the primary thread allows the process inside bgThread
to begin executing before the primary thread completes its work:
Figure 1.2 – Using Thread.Sleep to change the order of events
The two Thread.Sleep
intervals can be adjusted to see how they impact the console output. Give it a try!
Additionally, it is possible to pass Timeout.Infinite
to Thread.Sleep
. This will cause the thread to pause until it is interrupted or aborted by another thread or the managed environment. Interrupting a blocked or paused thread is accomplished by calling Thread.Interrupt
. When a thread is interrupted, it will receive a ThreadInterruptedException
exception.
The exception handler should allow the thread to continue working or clean up any remaining work. If the exception is unhandled, the runtime will catch the exception and stop the thread. Calling Thread.Interrupt
on a running thread will have no effect until that thread has been blocked.
Now that you understand how to create an interrupt thread, let’s wrap up this section by learning how to destroy a thread.
Destroying managed threads
Generally, destroying a managed thread is considered an unsafe practice. That is why .NET 6 no longer supports the Thread.Abort
method. In .NET Framework, calling Thread.Abort
on a thread would raise a ThreadAbortedException
exception and stop the thread from running. Aborting threads was not made available in .NET Core or any of the newer versions of .NET. If some code needs to be forcibly stopped, it is recommended that you run it in a separate process from your other code and use Process.Kill
to terminate the other process.
Any other thread termination should be handled cooperatively using cancelation. We will see how to do this in the Scheduling and canceling work section. Next, let’s discuss some of the exceptions to handle when working with managed threads.