Delegates are a few of the objects that make threading easier in .NET reference. They can be used to asynchronously invoke a method. What other objects exist in framework 4.5 (o
The Task
and Task<T>
, but they've been here since .NET 4. async
does not necessarily work with threads, see Jon's video from Øredev for a very good explanation.
Well let's see here:
ThreadPool
class - kinda old, but still reliable for a simple producer-consumer pattern.BackgoundWorker
(.NET 2.0+) - another old-school construct, providing useful features for executing tasks in the background in GUI applications.Timer
s - useful for executing code at specified intervals using a background thread.Task
class (.NET 4.0+) - threading abstractions that run on the underlying thread pool and provide many useful features like exception marshaling and scheduling. Useful for the so-called "task parallelism" pattern.Parallel.For
, Parallel.ForEach
(.NET 4.0+) - good for executing the same operation over a set of data in parallel. Useful for the so-called "data parallelism" pattern.Parallel.Invoke
(.NET 4.0+) - a further abstraction over Task
s. Simply fires off several pieces of code (methods, lambdas) in parallel.I tend to answer a lot of questions related to multithreading and I often see the same basic question asked in various different ways. I will present the most common problems as I have seen them over the years and explain how the newer technologies have made solving these problems easier.
Closing over the loop variable
This is not a problem specific to threading, but the use of threading definitely magnifies the problem. C# 5.0 fixes this problem for the foreach
loop by creating a new variable for each iteration. You will no longer have to create a special variable for lambda expression closures. Unfortunately, the for
loop will still need to be handle with a special capturing variable.
Waiting for asynchronous tasks to complete
.NET 4.0 introduced the CountdownEvent
class which encapsulates a lot of the logic required to wait for the completion of many tasks. Most junior developers used Thread.Join
calls or a single WaitHandle.WaitAll
call. Both of these have scalability problems. The old pattern was to use a single ManualResetEvent
and signal it when a counter reached zero. The counter was updated using the Interlocked
class. CountdownEvent
makes this pattern much easier. Just remember to treat your main as a worker as well to avoid that subtle race condition that can occur if one worker finishes before all workers have been queued.
.NET 4.0 also introduced the Task
class which can have child tasks chained off of it via TaskCreationOptions.AttachedToParent
. If you call Task.Wait
on a parent it will wait for all child tasks to complete as well.
Producer-Consumer
.NET 4.0 introduced the BlockingCollection
class which acts like a normal queue except that it can block when the collection is empty. You can queue an object by calling Add
and dequeue an object by calling Take
. Take
blocks until an item is available. This simplifies producer-consumer logic considerably. It used to be the case that developers were trying to write their own blocking queue class. But, if you do not know what you are doing then you can really screw it up...bad. In fact, for the longest time Microsoft had a blocking queue example in the MSDN documentation that was itself badly broken. Fortunately, it has since been removed.
Updating UI with worker thread progress
The introduction of BackgroundWorker
made spinning off a background task from a WinForm application a lot easier for novice developers. The main benefit is that you can call ReportProgress
from within the DoWork
event handler and the ProgressChanged
event handlers will be automatically marshaled onto the UI thread. Of course, anyone that tracks my answers on SO knows how I feel about marshaling operations (via Invoke
or the like) as a solution for updating the UI with simple progress information. I rip on it all the time because it is generally a terrible approach. BackgroundWorker
still forces the developer into a push model (via marshaling operations in the background), but at least it does all of this behind the scenes.
The inelegance of Invoke
We all know that a UI element can only be accessed from the UI thread. This generally meant that a developer had to use marshaling operations via ISynchronizeInvoke
, DispatcherObject
, or SynchronizationContext
to transfer control back to the UI thread. But lets face it. These marshaling operations look ugly. Task.ContinueWith
made this a little more elegant, but the real glory goes to await
as part of C# 5's new asynchronous programming model. await
can be used to wait for a Task
to complete in such a manner that flow control is temporarily interrupted while the task is running and then returned at that very spot in the right synchronization context. There is nothing more elegant and satisfying than using await
as a replacement for all those Invoke
calls.
Parallel programming
I often see questions asking how things can happen in parallel. The old way was to create a few threads or use the ThreadPool
. .NET 4.0 gave use the TPL and PLINQ. The Parallel
class is a great way to get the iterations of a loop going in parallel. And PLINQ's AsParallel
is a different side of the same coin for plain old LINQ. These new TPL features greatly simplify this category of multithreaded programming.
.NET 4.5 introduces the TPL Data Flow library. It is intended to make elegant an otherwise complex parallel programming problem. It abstracts classes into blocks. They can be target blocks or source blocks. Data can flow from one block to another. There are many different blocks including BufferBlock<T>
, BroadcastBlock<T>
, ActionBlock<T>
, etc. that all do different things. And, of course, the whole library will be optimized for use with the new async
and await
keywords. It is an exciting new set of classes that I think will slowly catch on.
Graceful termination
How do you get a thread to stop? I see this question a lot. The easiest way is to call Thread.Abort
, but we all know the perils of doing this...I hope. There are many different ways to do this safely. .NET 4.0 introduced a more unified concept called cancellation via CancellationToken
and CancellationTokenSource
. Background tasks can poll IsCancellationRequested
or just call ThrowIfCancellationRequested
at safe points to gracefully interrupt whatever work they were doing. Other threads can call Cancel
to request cancellation.
Without a doubt, getting to grips with the new Tpl DataFlow library (included in .net 4.5) will give you the biggest boost in terms of concurrent development.
If you're serious about highly concurrent apps, spend a day or two familiarizing yourself with DataFlow. It's seriously good.