ASP.NET multi-threading: Thread vs ThreadPool vs Task vs Parallel. Which one is the best choice?

I’m currently working on my upcoming ASP.NET 4.5 Expert Skills book, and it struck me that there are now at least 4 different options available to the programmer when implementing multi-threading. If you’re not already familiar with these classes, choosing between them is likely to be extremely difficult.

Let’s take some simple pseudo-code as an example:

foreach (var FileToEncrypt in FilesToEncrypt)
{
    EncryptFile(FileToEncrypt);
}

In this example, let’s imagine that the EncryptFile method is very processor-intensive, so we’d like to split the calls to EncryptFile into separate threads.

Using the Thread class

The Thread class has been around since the original release of the .NET framework, so  it’s likely that existing multi-threading code will use this class.

The Thread class is found in the System.Threading namespace.

Here’s how the multi-threaded code would look using the Thread class:

List<Thread> MyThreads = new List<Thread>();
foreach (var FileToEncrypt in FilesToEncrypt)
{
    Thread NewThread = new Thread(new ParameterizedThreadStart(FileToEncrypt));
    NewThread.Start(FileToEncrypt);
}

This looks fine as long as it is all that your program needs to do. But let’s assume that we need to do something with the encrypted files after the threads have finished encrypting them. We’ll need to add some code that waits until all of the threads have finished before continuing:

List<Thread> Threads = new List<Thread>();
foreach (string FileToEncrypt in FilesToEncrypt)
{
    Thread NewThread = new Thread(new ParameterizedThreadStart(FileToEncrypt));
    Threads.Add(NewThread);
    NewThread.Start();
    Stuff(FileToEncrypt);
}
bool AllThreadsFinished = false;
while (!AllThreadsFinished)
{
    AllThreadsFinished = true;
    foreach (Thread MyThread in Threads)
    {
        if (MyThread.IsAlive) AllThreadsFinished = false;
    }
    Thread.Sleep(1000);
}

We’ve had to add quite a lot of code to achieve it, but this code will now wait for all of the threads to finish. This isn’t the only way to achieve this using the Thread class, but it’s the easiest to understand and uses the least code.

The Thread class does a good job of splitting code into multiple threads, and will even benefit from the improvements to threading that were added to ASP.NET 4.5. If your existing code uses the Thread class, there’s really no need to change it.

Using the ThreadPool class

The ThreadPool class is another class that has been around since the first version of the .NET framework. ThreadPool offers more control over how threads are distributed to the computers processor or processors, but it is much harder to implement the ‘wait’ code that we used for the Thread class.

The ThreadPool class is found in the System.Threading namespace.

Here’s how our threading code would look using the ThreadPool class:

foreach (string FileToEncrypt in FilesToEncrypt)
{
    ThreadPool.QueueUserWorkItem(new WaitCallback(EncryptFile), FileToEncrypt);
}

This looks much easier, but the ThreadPool class doesn’t give us any way to check on the status of the thread that we just created, so how can we tell when it has finished?

There are some very complicated ways of extracting the status of the thread, but an alternative option is to maintain a counter that is incremented when the EncryptFile method finishes. We’ll call it: NumberOfFilesEncrypted

We can then wait for all of the threads to finish by using the following code:

foreach (string FileToEncrypt in FilesToEncrypt)
{
    ThreadPool.QueueUserWorkItem(new WaitCallback(EncryptFile), FileToEncrypt);
}
while (NumberOfFilesEncrypted < FilesToEncrypt.Count)
{
    Thread.Sleep(1000);
}

Of course, this is far from ideal. If one of the threads failed for some reason, the code would wait forever for the last thread to finish. This could be worked around with error handling and timeouts, but it’s really better to avoid the ThreadPool class if possible.

Using the new Task class

The Task class was introduced in .NET 4.0, but has only really been finalized in the .NET 4.5 release. The Task class combines the functionality of the Thread and ThreadPool classes to make threading easier and more efficient.

The Task class is found in the new System.Threading.Tasks namespace.

Here’s how our code would look using the Task class:

foreach (string FileToEncrypt in FilesToEncrypt)
{
    Task.Factory.StartNew(Argument => EncryptFile(Argument), FileToEncrypt);
}

Once again, of course, we need to check whether all of the threads have finished before continuing. We can do this in a very similar way to the Thread class, but with much less code:

List<Task> MyTasks = new List<Task>();
foreach (string FileToEncrypt in FilesToEncrypt)
{
    MyTasks.Add(Task.Factory.StartNew(Argument => EncryptFile(Argument), FileToEncrypt));
}
Task.WaitAll(MyTasks.ToArray());

The Task.WaitAll method makes everything much easier! If you have the choice between the Thread and Task classes, I’d recommend the Task class every time.

However, I’ve saved the best for last. The Parallel class enables us to carry out the task with even less code.

Using the new Parallel class

The Parallel class enables you to change any for or foreach loop into multi-threaded code. Every iteration of the loop will be intelligently assigned to a separate thread and will be executed in the most efficient way possible.

The Parallel class can be found in the System.Threading.Tasks namespace.

Here’s how our code would look using the Parallel class:

Parallel.ForEach(FilesToEncrypt, FileToEncrypt =>
{
    EncryptFile(FileToEncrypt);
});

You might be wondering how we wait for all of the threads to finish. In fact, the Parallel class handles this automatically! The foreach loop won’t finish until all of the threads have finished running.

If the code that you want to be multi-threaded can be expressed as a for or foreach loop, there’s little doubt that you want to use the Parallel class.

If you’re using ASP.NET 4.5, I would highly recommend using the Task class and Parallel classes and ignoring the older Thread and ThreadPool classes.

Leave a Reply

Your email address will not be published.

Blue Captcha Image
Refresh

*