Sunday, April 01, 2012

Do I need to dispose of Tasks?

MSDN Blogs > Parallel Programming with .NET > Do I need to dispose of Tasks?
Do I need to dispose of Tasks?

 Stephen Toub - MSFT 25 Mar 2012 11:17 PM 6
I get this question a lot:

“Task implements IDisposable and exposes a Dispose method.  Does that mean I should dispose of all of my tasks?”

Summary

Here's my short answer to this question: 
“No.  Don’t bother disposing of your tasks.”

Here’s my medium-length answer: 
“No.  Don’t bother disposing of your tasks, not unless performance or scalability testing reveals that you need to dispose of them based on your usage patterns in order to meet your performance goals.  If you do find a need to dispose of them, only do so when it’s easy to do so, namely when you already have a point in your code where you’re 100% sure that they’re completed and that no one else is using them.”

And for those of you looking for a coffee-break read, here’s my long answer…

Why Task.Dispose?

At a high-level, the .NET Framework Design guidelines state that a type should implement IDisposable if it in turn holds onto other IDisposable resources.  And Task does.  Internally, Task may allocate a WaitHandle which can be used to wait on the Task to complete.  WaitHandle is IDisposable because it holds onto a SafeWaitHandle internally, which is IDisposable.  SafeWaitHandle wraps a native handle resource: if the SafeWaitHandle isn’t disposed, eventually its finalizer will get around to cleaning up the wrapped handle, but in the meantime its resources won’t be cleaned up and pressure will be put on the system.  By implementing IDisposable on Task, then, we enable developers concerned about aggressively cleaning up these resources to do so in a timely manner.

The problems

If every single Task allocated a WaitHandle, then it’d be a good idea for performance reasons to aggressively Dispose of Tasks.  But that’s not the case.  In reality, very few tasks actually have their WaitHandle allocated. In .NET 4, the WaitHandle was lazily-initialized in a few situations: if the Task’s ((IAsyncResult)task).AsyncWaitHandle explicitly-implemented interface property was accessed, or if the Task was used as part of a Task.WaitAll or Task.WaitAny call and the Task was not yet completed.  This made the answer to the “should I dispose” question slightly difficult, as if you had a lot of tasks being used with WaitAll/WaitAny, it might actually have been good to dispose of those tasks.

Also in .NET 4, once a Task was disposed, most of its members threw ObjectDisposedExceptions if they were accessed.  This made it difficult to cache completed tasks (which might be done for performance reasons), because if one consumer disposed of the task, another consumer would be unable to access important members of the task, like ContinueWith or its Result.

There’s another complication here, and that is that Tasks are fundamentally asynchronous primitives.  If the tasks are being used for parallelism, such as in a fork/join pattern, then it’s often easy to know when you’re done with them and when no one else is using them, e.g.

var tasks = new Task[3]; 
tasks[0] = Compute1Async(); 
tasks[1] = Compute2Async(); 
tasks[2] = Compute3Async(); 
Task.WaitAll(tasks); 
foreach(var task in tasks) task.Dispose();

However, when using tasks for sequencing asynchronous operations, it’s often more difficult.  For example:

Compute1Async().ContinueWith(t1 => 
    t1.Dispose(); 
    … 
});

QR: Inline image 1

Posted via email from Jasper-Net