ThreadPool vs. Tasks
.NET 4.0 includes a new few new classes called Tasks, which are part of the Task Parallel Library. You can learn all about them in an article by my friend Sacha on Code Project.
The TPL is useful, but I'm starting to see a lot of coders using the Task class. I may be an old fuddy-duddy, but I can't quite understand what advantage Task gives me over plain old ThreadPool in .NET 2.0. Here are some examples of how the Tasks "features" would be implemented using ThreadPool.
Starting a task
ThreadPool.QueueUserWorkItem(delegate
{
DoSomeWork();
});
Waiting for a task to complete
var handle = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(delegate
{
DoSomeWork();
handle.Set();
});
handle.WaitOne();
Returning
int result = 0;
ThreadPool.QueueUserWorkItem(delegate
{
DoSomeWork();
result = 42;
handle.Set();
});
handle.WaitOne();
Console.WriteLine(result);
Chaining tasks
var handle = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(delegate
{
DoSomeWork();
handle.Set();
});
handle.WaitOne();
ThreadPool.QueueUserWorkItem(delegate
{
DoSomeMoreWork();
handle.Set();
});
handle.WaitOne();
Waiting for multiple tasks to complete
var handle1 = new ManualResetEvent(false);
var handle2 = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(delegate
{
DoSomeWork();
handle1.Set();
});
ThreadPool.QueueUserWorkItem(delegate
{
DoSomeMoreWork();
handle2.Set();
});
WaitHandle.WaitAll(new WaitHandle[] { handle1, handle2 });
Exception handling
var handle = new ManualResetEvent(false);
Exception error = null;
ThreadPool.QueueUserWorkItem(delegate
{
try
{
DoSomeWork();
}
catch (Exception ex)
{
error = ex;
}
finally
{
handle.Set();
}
});
handle.WaitOne();
if (error != null)
Console.WriteLine("Error! " + error);
Word of caution: you should probably always do this when using ThreadPool, since exceptions thrown on a ThreadPool thread will tear down the AppDomain if not caught
Cancellation
var cancel = false;
ThreadPool.QueueUserWorkItem(delegate
{
while (!cancel)
{
Thread.Sleep(100);
}
});
cancel = true;
Note: this is like a gazillion times more complex in Tasks
Dispatching to UI thread
var dispatcher = Application.Current.Dispatcher;
ThreadPool.QueueUserWorkItem(delegate
{
DoSomeWork();
dispatcher.BeginInvoke(new Action(() => progressBar.Value += 10));
DoSomeMoreWork();
dispatcher.BeginInvoke(new Action(() => progressBar.Value += 10));
});
Being notified when a task is complete without blocking
Action<int> done = (int x) => Console.WriteLine("Done! " + x);
ThreadPool.QueueUserWorkItem(delegate
{
DoSomeWork();
done(42);
});
So help me learn the Task API - how would using Task make the examples above look better?
Trackbacks
- Tasks vs ThreadPool | sachabarber.net | http://sachabarber.net/?p=983
No new comments are allowed on this post.
Comments
wekempf
Wow, Paul, first post where I don't think you get it at all. :)
First, several of your examples are wrong. For instance, your example on "Chaining Tasks" didn't chain anything. You made one of the worst sins you can make with that example: you turned asynchronous code into synchronous code. With Task.ContinueWith everything remains asynchronous.
Then there's the fact that nearly every single one of your examples is a lot more complex than the equivalent example done using Task. More complex means more chance of error, and errors in asynchronous code are very difficult to deal with.
Also, there's performance concerns. A lot of the patterns you're using are simply not as performant as the equivalent code using Task.
Now let's also think about the future. The Async CTP is amazing, and it simply couldn't be done without Task.
Task simplifies a lot of code, adds uniformity (cancellation uniformity means that library code you call can participant in cancellation, which won't work with your example), enables composition and allows extensions along the lines of Async CTP.
Oh, and I choked on the "this is like a gazillion times more complex in Tasks" comment. Really? I think it's simpler with Task, and no one can seriously argue it's a "gazillion times" more complex!
I post something to my blog later illustrating how every one of your examples is simpler with Task.
Mike Strobel
I've grown rather fond of Rx. It works particularly well for scenarios where there's no need to block while waiting for results. It has a nice
ISchedulerinterface and a set of standard schedulers, including a ThreadPoolScheduler (and a TaskPoolScheduler).Some of your examples reworked to use Rx/ThreadPoolScheduler:
Starting a task
Waiting for a task to complete
Returning
Chaining tasks
Waiting for multiple tasks to complete
Exception handling
Being notified when a task is complete without blocking
Cameron MacFarland
Tasks is like LINQ.
Sure you can do the equivalent thing with a for loop and an if statement, but LINQ gives you a simpler syntax.
Simpler syntax means fewer bugs and easier to understand code.
wekempf
I wrote my blog post and you can find it here: http://digitaltapestry.net/blog/threadpool-vs.-tasks.
Paul, does this help you to understand why you should get on the Task bandwagon?
James Miles
Paul, your Cancellation example worries me. On face value, this operation isn't suitable for ThreadPool or TaskPool!
Could you provide some more information?
You're blocking a ThreadPool thread. This will have a negative impact on any other operations enqueued on the ThreadPool. I suspect that your real use case wouldn't involve a Thread.Sleep(100) but I can only guess.
Tasks are designed for asynchronous / cooperative threading, while(!cancelled) sounds more like a dedicated thread to me.
sacha
Ha Ha I wonder if our conversation at the pub had anything to do with this ;-)
Quinton
Perhaps Paul has many fond memories of the old Threadpool and like myself doesn't want to let go of it yet as a few old projects are still using it. There is also the slight lurning curve moving over to Tasks and Linq, well personally for myself there was, hehe.
Kevin Jones
Hmmm. You're using Events which are really expensive, they're kernel objects and should be avoided unless necessary such as in cross-process synchronization.
Cancellation in the Task APIs will cancel tasks before they've been scheduled, unlike your code where the task will only be cancelled after it's consumed a thread
Caleb Vear
@Mike Strobel
Your example on chaining won't work if you remove the First() call.
You need to make it this instead.
Q
How would you pass a parameter to DoSomeWork? I don't see how, appears task must be stateless?