Changes and Guidance for the Task Parallel Library in .NET 4.5
Under .NET 4.0, the Task class exposes the IDisposable interface. The reason Task is disposable is to clean up the wait handle exposed by the AsyncWaitHandle property of the IAsyncResult interface. In .NET 4.0 this wait handle is only created when the AsyncWaitHandle property is read or if either Task.WaitAll or Task.WaitAny is used. The rest of the time calling Task.Dispose is unnecessary.
Unfortunately in .NET 4.0 the Task class is overly aggressive when it comes to throwing object disposed exceptions Dispose. Once you invoke Dispose, the rest of the object becomes unusable even though none of the other properties have anything to do with the wait handle that was released.
Should I invoke Task.Dispose in .NET 4.0?
No, unless all of the following is true:
- The completed Task will not be cached
- The wait was created by calling Task.WaitAll or Task.WaitAny, or by reading IAsyncResult.AsyncWaitHandle
- No other task or thread is waiting on the Task in question
And even if all of these requirements are met, you may find that the finalizer is doing a reasonably effective job of cleaning up the wait handles. So unless you are seeing some performance problems, you can still probably get away with not disposing tasks.
Changes for .NET 4.5 Core
In .NET 4.5 the internal wait handle is only created if you explicitly read the IAsyncResult.AsyncWaitHandle property. Everything else, including Task.WaitAll and Task.WaitAny, has been redesigned to no longer need it. And with the addition of language support for async/await, even IAsyncResult is no longer needed for most scenarios.
Another change is Task in .NET 4.5 is the task usable after disposing it. According to Stephen Toub, “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.”
So while it is safer to call Task.Dispose in .NET 4.5, there is almost never a reason to need to.
Special Rules for .NET 4.5 Metro
Stephen Toub then goes on to say that Task.Dispose doesn’t even exist if you are using the “.NET for Metro style apps” profile. Note that the WinRT documentation for Task has not yet been updated to reflect this design change.
Returning Task/Task<T> from functions
In a separate article titled “Should I expose asynchronous wrappers for synchronous methods?” Stephen discusses the topic of returning Task objects from functions in depth. While we encourage you to read the whole article, for those in a hurry here is his summary,
I believe the only asynchronous methods that should be exposed are those that have scalability benefits over their synchronous counterparts. Asynchronous methods should not be exposed purely for the purpose of offloading: such benefits can easily be achieved by the consumer of synchronous methods using functionality specifically geared towards working with synchronous methods asynchronously, e.g. Task.Run.
Brandon Holt, Preston Briggs, Luis Ceze, Mark Oskin May 21, 2015
Kai Kreuzer, Olaf Weinmann May 21, 2015