ThreadPool vs. Tasks

This is an old post and doesn't necessarily reflect my current thinking on a topic, and some links or images may not work. The text is preserved here for posterity.

.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?