Featured in
Architecture & Design

Mini-talks: The Machine Intelligence Landscape: A Venture Capital Perspective by David Beyer. The future of global, trustless transactions on the largest graph: blockchain by Olaf Carlson-Wee. Algorithms for Anti-Money Laundering by Richard Minerich.

Featured in
Operations & Infrastructure

Mini-talks: The Machine Intelligence Landscape: A Venture Capital Perspective by David Beyer. The future of global, trustless transactions on the largest graph: blockchain by Olaf Carlson-Wee. Algorithms for Anti-Money Laundering by Richard Minerich.

Featured in
Enterprise Architecture

Mini-talks: The Machine Intelligence Landscape: A Venture Capital Perspective by David Beyer. The future of global, trustless transactions on the largest graph: blockchain by Olaf Carlson-Wee. Algorithms for Anti-Money Laundering by Richard Minerich.

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.

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.