If you’ve been following the development of the Task Parallel Library (TPL), or if you’re an avid reader of this blog, you’re likely aware that the default scheduler in TPL is based on the new and improved ThreadPool in .NET 4. In light of this, though, quite a few folks have asked whether there are any advantages (particularly in regards to performance) to sticking with the existing ThreadPool APIs, e.g. QueueUserWorkItem. This post attempts to briefly answer that question.
First, if your scenario requires the new functionality that Tasks provide, it’s best to use TPL. Not only does this save you the time for rolling and optimizing your own solution, but TPL also has the advantage of being able to hook into internals of the ThreadPool that are not exposed publicly and that can boost performance in a variety of key scenarios. For example, if you need to wait on your work items, Task.Wait (and its variants) benefits from tight integration with the ThreadPool’s work-stealing queues. The story for other features like continuations and cancellation is similar.
Now, if your scenario does not require TPL’s features, such that your workloads are primarily fire-and-forget asynchronous work, then using QueueUserWorkItem may be appropriate. Whether you want to do so, though, still depends on the specific workload. On the one hand, as mentioned above, Tasks take advantage of the new work-stealing queues in the ThreadPool, which can help avoid issues of contention and cache coherency, both of which can lead to performance degradation. On the other hand, additional functionality necessarily means additional overhead. Using Tasks results in larger object allocations, and maintaining a Task’s lifecycle (status, cancellation requests, exception handling, etc.) requires extra work and synchronization. As a result, there are surely some scenarios that will run faster using QueueUserWorkItem.
In conclusion, I’ll reiterate what the CLR team’s ThreadPool developer has already stated:
“Task is now the preferred way to queue work to the thread pool.”
In other words, we recommend that you start with TPL for all of your asynchronous needs. Afterwards, if you suspect that your scenario would benefit from something more lightweight, it should be easy to try out QueueUserWorkItem. If that works better, make the change – it won’t hurt our feelings =).