Is it considered acceptable to not call Dispose() on a TPL Task object?

31,148

Solution 1

There is a discussion about this in the MSDN forums.

Stephen Toub, a member of the Microsoft pfx team has this to say:

Task.Dispose exists due to Task potentially wrapping an event handle used when waiting on the task to complete, in the event the waiting thread actually has to block (as opposed to spinning or potentially executing the task it's waiting on). If all you're doing is using continuations, that event handle will never be allocated
...
it's likely better to rely on finalization to take care of things.

Update (Oct 2012)
Stephen Toub has posted a blog titled Do I need to dispose of Tasks? which gives some more detail, and explains the improvements in .Net 4.5.

In summary: You don't need to dispose of Task objects 99% of the time.

There are two main reasons to dispose an object: to free up unmanaged resources in a timely, deterministic way, and to avoid the cost of running the object's finalizer. Neither of these apply to Task most of the time:

  1. As of .Net 4.5, the only time a Task allocates the internal wait handle (the only unmanaged resource in the Task object) is when you explicitly use the IAsyncResult.AsyncWaitHandle of the Task, and
  2. The Task object itself doesn't have a finalizer; the handle is itself wrapped in an object with a finalizer, so unless it's allocated, there's no finalizer to run.

Solution 2

This is the same kind of issue as with the Thread class. It consumes 5 operating system handles but does not implement IDisposable. Good decision of the original designers, there are of course few reasonable ways to call the Dispose() method. You'd have to call Join() first.

The Task class adds one handle to this, an internal manual reset event. Which is the cheapest operating system resource there is. Of course, its Dispose() method can only release that one event handle, not the 5 handles that Thread consumes. Yeah, don't bother.

Do beware that you ought to be interested in the task's IsFaulted property. It's a fairly ugly topic, you can read more about it in this MSDN Library article. Once you deal with this properly, you should also have a good spot in your code to dispose the tasks.

Share:
31,148
Didier Supernover
Author by

Didier Supernover

CTO at Contigo Software Github projects: Here and here Contact me by emailing my username at gmail dot com.

Updated on April 25, 2021

Comments

  • Didier Supernover
    Didier Supernover about 3 years

    I want to trigger a task to run on a background thread. I don't want to wait on the tasks completion.

    In .net 3.5 I would have done this:

    ThreadPool.QueueUserWorkItem(d => { DoSomething(); });
    

    In .net 4 the TPL is the suggested way. The common pattern I have seen recommended is:

    Task.Factory.StartNew(() => { DoSomething(); });
    

    However, the StartNew() method returns a Task object which implements IDisposable. This seems to be overlooked by people who recommend this pattern. The MSDN documentation on the Task.Dispose() method says:

    "Always call Dispose before you release your last reference to the Task."

    You can't call dispose on a task until it is completed, so having the main thread wait and call dispose would defeat the point of doing on a background thread in the first place. There also doesn't seem to be any completed/finished event that could be used for cleanup.

    The MSDN page on the Task class doesn't comment on this, and the book "Pro C#2010..." recommends the same pattern and makes no comment on task disposal.

    I know if I just leave it the finalizer will catch it in the end, but is this going to come back and bite me when I'm doing lots of fire & forget tasks like this and the finalizer thread gets overwhelmed?

    So my questions are:

    • Is it acceptable to not call Dispose() on the Task class in this case? And if so, why and are there risks/consequences?
    • Is there any documentation that discusses this?
    • Or is there an appropriate way of disposing of the Task object that I've missed?
    • Or is there another way of doing fire & forget tasks with the TPL?
  • Didier Supernover
    Didier Supernover over 13 years
    Thanks, interesting. It goes against the MSDN documentation though. Is there any official word from MS or the .net team that this is acceptable code. There is also the point raised at the end of that discussion that "what if the implementation changes in a future version"
  • Didier Supernover
    Didier Supernover over 13 years
    Actually, I've just noticed that the answerer in that thread does indeed work at microsoft, seemingly on the pfx team, so I suppose this is an official answer of sorts. But there is suggestion towards the bottom of it not working in all cases. If there is a potential leak am I better of just reverting to ThreadPool.QueueUserWorkItem which I know is safe?
  • Insomniac
    Insomniac over 13 years
    Yes, it is very strange that there is a Dispose that you might not call. If you take a look at samples here msdn.microsoft.com/en-us/library/dd537610.aspx and here msdn.microsoft.com/en-us/library/dd537609.aspx they're not disposing tasks. However code samples in MSDN sometimes demonstrate very bad techniques. Also the guy answered on the question works for Microsoft.
  • Richard
    Richard over 13 years
    @Simon: (1) The MSDN doc you quote is the generic advice, specific cases have more specific advice (e.g. not needing to use EndInvoke in WinForms when using BeginInvoke to run code on the UI thread). (2) Stephen Toub is quite well know as a regular speaker on effective use of PFX (e.g. on channel9.msdn.com), so if anyone can give good guidance then he's it. Note his second paragraph: there are times leaving things to the finaliser is better.
  • Richard
    Richard over 13 years
    Of course that fails to dispose of the Task instance returned by ContinueWith, but see the quote from Stephen Toub is the accepted answer: there is nothing to dispose if nothing performs a blocking wait on a task.
  • Didier Supernover
    Didier Supernover over 13 years
    As Richard mentions, ContinueWith(...) also returns a second Task object which then doesn't get disposed.
  • Chris Marisic
    Chris Marisic over 13 years
    So as this stands the ContinueWith code is actually worse than redudant since it will cause another task to be created just to dispose of the old task. With the way this stands it'd basically not be possible to introduce a blocking wait into this code block aside from if the action delegate you passed into it was attempting to manipulate Tasks itself also correct?
  • svick
    svick over 12 years
    But a task doesn't create a Thread in most cases, it uses the ThreadPool.
  • Gideon Engelberth
    Gideon Engelberth over 12 years
    You may be able to use how lambdas capture variables in a slightly tricky way to take care of the second task. Task disper = null; disper = tsk.ContinueWith(cnt => { cnt.Dispose(); disper.Dispose(); });
  • Chris Marisic
    Chris Marisic over 12 years
    @GideonEngelberth that would seemingly have to work. Since disper should never get disposed of by the GC it should remain valid until the lambda invokes itself to dispose, assuming the reference is still valid there /shrug. Maybe needs an empty try/catch around it?
  • Gideon Engelberth
    Gideon Engelberth over 12 years
    @ChrisMarisic Only if the TPL keeps its own cache of running tasks, which it may well do. Otherwise there is no reference to disper except from itself, so the GC could collect disper.
  • user11658885
    user11658885 about 4 years
    Thanks! The ContinueWith(cnt => cnt.Dispose()) turned out to be the only thing that worked to fix memory overload and performance lag in super high-load env. The impact of this setting was paramount.