在.NET 4.0 中,Task 类暴露了 IDisposable 接口。Task 可被回收(disposable)是为了清理 IAsyncResult 接口中 AsyncWaitHandle 属性暴露的等待句柄(wait handle)。在.NET 4.0 中,等待句柄只有在读取 AsyncWaitHandle 属性,或者使用 Task.WaitAll、Task.WaitAny 时才会被创建,其他情况调用 Task.Dispose 都是多余的。
遗憾的是,.NET 4.0 中的 Task 在处理 ObjectDisposedException 时显得过于武断:一旦调用 Dispose 释放等待句柄之后,即使其他属性与之毫无联系,剩余对象也会变得不稳定。
那么在.NET 4.0 中是否应该调用 Task.Dispose?
不应该,除非遇到以下情况:
- 整个 Task 不会被缓存;
- 等待句柄是通过调用 Task.WaitAll、Task.WaitAny,或是读取 IAsyncResult.AsyncWaitHandle 创建而成;
- Task 上没有其他任务或线程处于等待状态。
其实,即使所有的条件都满足,你也不用做什么,因为终结器 (finalizer)在清理等待句柄方面已经做了相同高效的工作。所以,除非你看到一些性能问题,否则你也许可以仍然不用回收 task。
.NET 4.5 核心中的改动
在.NET 4.5 中,只有显式读取 IAsyncResult.AsyncWaitHandle 时,内部等待句柄才会被创建。其他部分,包括 Task.WaitAll 和 Task.WaitAny 都进行了重新设计,它们不再需要等待句柄。另外,随着语言中对 async/await 的支持,IAsyncResult 在大部分场景中甚至都不再需要。
.NET 4.5 中 Task 的另外一个改动是 task 在释放之后仍然可用。按照Stephen Toub 的说法,“现在,即使Task 释放之后也可以使用它的所有公开成员,并且它们使用起来就和释放之前一样。唯一一个不能使用的成员是IAsyncResult.AsyncWaitHandle,因为它是Task 实例真正释放的部分。如果试图在Task 释放后访问该属性,它会抛出一个ObjectDisposedException 异常。”
虽然在.NET 4.5 中调用Task.Dispose 变得更加安全,但是几乎没有理由需要这么做。
针对.NET 4.5 Metro 的特殊规则
Stephen Toub 接着提到 Task.Dispose 在“.NET Metro 风格应用程序”框架中甚至并不存在。要注意的是,目前关于此项设计变更的信息还未在 WinRT 的 Task 文档中更新反映。
从函数返回 Task
在另外一篇题为“是否应当为同步方法暴露异步包装?“的文章中,Stephen 深度探讨了从函数返回 Task 对象的话题。我们推荐你阅读全篇文章,而如果你时间不充裕,可以阅读以下的总结部分:
我认为只有那些异步方法比对应的同步方法拥有可扩展性(scalability)优势时才应当被暴露。异步方法不应当为了单纯地减轻负载(offloading)而进行暴露,因为这类优势可以通过使用专门用于异步执行同步方法的功能轻松实现,如使用 Task.Run。
查看英文原文: Changes and Guidance for the Task Parallel Library in .NET 4.5
评论