Do I need to dispose of Tasks?

Do I need to dispose of Tasks?

Rate This
  • Comments 14

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();
    …
});

This example successfully disposes of the Task returned from Compute1Async, but it neglects to Dispose of the Task returned from ContinueWith.  We could of course dispose of that one, too:

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

but then we’re not disposing of the Task returned from the second ContinueWith.  You get the idea.  Even with the new async/await keywords in C# and Visual Basic, it’s still kludgy.  Consider a basic sequencing of operations, e.g.

string s1 = await Compute1Async();
string s2 = await Compute2Async(s1);
string s3 = await Compute3Async(s2);

If I wanted to Dispose of those Tasks, I’d need to rewrite this as something like the following:

string s1 = null, s2 = null, s3 = null;
using(var t1 = Compute1Async())
    s1 = await t1;
using(var t2 = Compute2Async(s1))
    s2 = await t2;
using(var t3 = Compute3Async(s2))
    s3 = await t3;

Awkward.

The solutions

Given the the lack of a need to dispose of most tasks and the awkwardness of doing so, for .NET 4.5 we’ve made several changes regarding Task.Dispose:

  1. We’ve made it much less likely that the Task’s WaitHandle will be allocated at all.  We’ve re-implemented WaitAll and WaitAny so that they don’t rely on a Task’s WaitHandle, and we’ve avoided using it internally for any of the new Task or async/await-related functionality introduced in .NET 4.5.  Thus, the only way the WaitHandle will be allocated is if you explicitly ask for the Task’s IAsyncResult.AsyncWaitHandle, and that should be quite rare.  This means that except in such very infrequent circumstances, disposing of a Task is completely unnecessary.
  2. We’ve made Tasks usable even after they’ve been disposed.  You can now use all of the public members of Task even after its disposal, and they’ll behave just as they did before disposal.  The only member you can’t use is IAsyncResult.AsyncWaitHandle, since that’s what actually gets disposed when you dispose of a Task instance; that property will continue to throw an ObjectDisposedException if you try to use it after the Task has been disposed.  This means you should feel freely comfortable caching completed Tasks, knowing that they’re observationally pure.  Additionally, moving forward, IAsyncResult usage should drop significantly now that we have async/await and the Task-based Async Pattern, and even for continued usage of IAsyncResult, usage of its AsyncWaitHandle is quite rare.
  3. For the new “.NET for Metro style apps” reference assemblies and surface area, Task doesn’t even implement IDisposable.  So for Metro style apps, or for portable libraries that span these apps, you don’t even have the option of disposing of Tasks, and that’s a good thing.

The guidance

So, this brings us back to the short answer: “No. Don’t bother disposing of your tasks.”  It’s often difficult to find a good place to do so, there’s almost never a reason to do so, and depending on your reference assemblies, you might not even be able to do so.

Leave a Comment
  • Please add 6 and 1 and type the answer here:
  • Post
  • What about calling Wait(). Does wait clean up the handle after the wait is completed?

  • The documentation at msdn.microsoft.com/.../dd270681(v=vs.110).aspx states the opposite: "Always call Dispose before you release your last reference to the Task. Otherwise, the resources it is using will not be freed until the garbage collector calls the Task object's Finalize method."

    I guess that warning needs to be removed, and preferably replaced by an explanation that calling Dispose is not necessary in most cases.

  • tobi, using Wait() does not use the Task's WaitHandle, neither in .NET 4 nor in .NET 4.5.

    Wim, yes.  That's standard boilerplate text added to all MSDN documentation for types that implement IDisposable.  We're looking into removing it from Task's docs.  It's not only the wrong guidance in this case, but actually factually incorrect: Task doesn't have a finalizer.

  • Wait seems to use CompletedEvent which is likely to allocate an event handle.

  • tobi, no, it's not likely to. The CompletedEvent internal property you're referring to returns a ManualResetEventSlim.  Its Wait method will not force the WaitHandle to be allocated.

  • This is great news, the thought of trying to dispose tasks always seemed like one of the leakiest abstractions ever.

  • Seeing IDisposable with Task always forced us to think calling Dispose(). Thanks for sharing this.

  • How about disposing CancellationTokenSource?

  • Hi Alex-

    It depends.

    In .NET 4, CTS.Dispose served two primary purposes.  If the CancellationToken's WaitHandle had been accessed (thus lazily allocating it), Dispose will dispose of that handle.  Additionally, if the CTS was created via the CreateLinkedTokenSource method, Dispose will unlink the CTS from the tokens it was linked to.  In .NET 4.5, Dispose has an additional purpose, which is if the CTS uses a Timer under the covers (e.g. CancelAfter was called), the Timer will be Disposed.

    It's very rare for CancellationToken.WaitHandle to be used, so cleaning up after it typically isn't a great reason to use Dispose.  If, however, you're creating your CTS with CreateLinkedTokenSource, or if you're using the CTS' timer functionality, it can be more impactful to use Dispose.

  • Thanks very much Stephen for clearing up a grey area. Saved me from writing an ugly timer to poll a collection of tasks every 30secs to check to see if they're complete and dispose of them 1 by 1.

  • "We’ve made Tasks usable even after they’ve been disposed."

    Do you really think this was a good decision. "Disposed" is in the mindset of the Developer as "Don't touch it anymore or little kittens die."

  • @Mike: Yes, I do, for all of the reasons outlined in this post.

  • Stephen, you wrote: "Task doesn't have a finalizer".

    But wait... unobserved exceptions are discovered when a task's finalizer runs? Or is there some other helper object's finalizer which does this job? If so how is its lifetime managed?

  • @Sami: Task does not have a finalizer. However, if the Task faults, it then allocates a finalizable object which it maintains a reference to.  That way, if the Task is no longer reachable, the finalizable object it allocated is no longer reachable either.

Page 1 of 1 (14 items)