A Basic Thread
- by Joe Mayo
Most of the programs written are single-threaded, meaning that they run on the main execution thread. For various reasons such as performance, scalability, and/or responsiveness additional threads can be useful. .NET has extensive threading support, from the basic threads introduced in v1.0 to the Task Parallel Library (TPL) introduced in v4.0. To get started with threads, it's helpful to begin with the basics; starting a Thread.
Why Do I Care?
The scenario I'll use for needing to use a thread is writing to a file. Sometimes, writing to a file takes a while and you don't want your user interface to lock up until the file write is done. In other words, you want the application to be responsive to the user.
How Would I Go About It?
The solution is to launch a new thread that performs the file write, allowing the main thread to return to the user right away. Whenever the file writing thread completes, it will let the user know. In the meantime, the user is free to interact with the program for other tasks. The following examples demonstrate how to do this.
Show Me the Code?
The code we'll use to work with threads is in the System.Threading namespace, so you'll need the following using directive at the top of the file:
using System.Threading;
When you run code on a thread, the code is specified via a method. Here's the code that will execute on the thread:
private static void WriteFile()
{
Thread.Sleep(1000);
Console.WriteLine("File Written.");
}
The call to Thread.Sleep(1000) delays thread execution. The parameter is specified in milliseconds, and 1000 means that this will cause the program to sleep for approximately 1 second. This method happens to be static, but that's just part of this example, which you'll see is launched from the static Main method. A thread could be instance or static.
Notice that the method does not have parameters and does not have a return type. As you know, the way to refer to a method is via a delegate. There is a delegate named ThreadStart in System.Threading that refers to a method without parameters or return type, shown below:
ThreadStart fileWriterHandlerDelegate =
new ThreadStart(WriteFile);
I'll show you the whole program below, but the ThreadStart instance above goes in the Main method. The thread uses the ThreadStart instance, fileWriterHandlerDelegate, to specify the method to execute on the thread:
Thread fileWriter = new Thread(fileWriterHandlerDelegate);
As shown above, the argument type for the Thread constructor is the ThreadStart delegate type. The fileWriterHandlerDelegate argument is an instance of the ThreadStart delegate type. This creates an instance of a thread and what code will execute, but the new thread instance, fileWriter, isn't running yet. You have to explicitly start it, like this:
fileWriter.Start();
Now, the code in the WriteFile method is executing on a separate thread. Meanwhile, the main thread that started the fileWriter thread continues on it's own. You have two threads running at the same time.
Okay, I'm Starting to Get Glassy Eyed. How Does it All Fit Together?
The example below is the whole program, pulling all the previous bits together. It's followed by its output and an explanation.
using System;
using System.Threading;
namespace BasicThread
{
class Program
{
static void Main()
{
ThreadStart fileWriterHandlerDelegate =
new ThreadStart(WriteFile);
Thread fileWriter = new Thread(fileWriterHandlerDelegate);
Console.WriteLine("Starting FileWriter");
fileWriter.Start();
Console.WriteLine("Called FileWriter");
Console.ReadKey();
}
private static void WriteFile()
{
Thread.Sleep(1000);
Console.WriteLine("File Written");
}
}
}
And here's the output:
Starting FileWriter
Called FileWriter
File Written
So, Why are the Printouts Backwards?
The output above corresponds to Console.Writeline statements in the program, with the second and third seemingly reversed. In a single-threaded program, "File Written" would print before "Called FileWriter". However, this is a multi-threaded (2 or more threads) program. In multi-threading, you can't make any assumptions about when a given thread will run. In this case, I added the Sleep statement to the WriteFile method to greatly increase the chances that the message from the main thread will print first. Without the Thread.Sleep, you could run this on a system with multiple cores and/or multiple processors and potentially get different results each time.
Interesting Tangent but What Should I Get Out of All This?
Going back to the main point, launching the WriteFile method on a separate thread made the program more responsive. The file writing logic ran for a while, but the main thread returned to the user, as demonstrated by the print out of "Called FileWriter". When the file write finished, it let the user know via another print statement. This was a very efficient use of CPU resources that made for a more pleasant user experience.
Joe