在查看各种 C # 异步 CTP 示例时,我看到了一些返回 void的异步函数,以及一些返回非通用 Task的异步函数。我可以理解为什么返回 Task<MyType>对于在异步操作完成时向调用者返回数据是有用的,但是我看到的返回类型为 Task的函数从来不返回任何数据。为什么不返回 void?
void
Task
Task<MyType>
如果调用方希望等待任务或添加继续。
事实上,返回 void的唯一原因是,如果 不能返回 Task,因为您正在编写一个事件处理程序。
类型 Task<T>是任务并行库(TPL)的主力类型,它表示“某些工作/作业将在未来产生类型 T的结果”的概念。“将在未来完成但不返回结果的工作”的概念由非泛型 Task 类型表示。
Task<T>
T
准确地说,T类型的结果将如何产生,以及特定任务的实现细节; 工作可能被外包到本地机器上的另一个进程、另一个线程等。TPL 任务通常从当前进程的线程池外包给工作线程,但是实现细节并不是 Task<T>类型的基础; 相反,Task<T>可以表示任何产生 T的高延迟操作。
根据你上面的评论:
await表达式意味着“计算这个表达式以获得一个对象,该对象表示将在未来产生结果的工作。将当前方法的其余部分注册为与该任务的继续相关联的回调。一旦这个任务生成并且回调被注册,马上就将控制权返回给我的调用者”。这与常规方法调用相反,常规方法调用的意思是“记住你在做什么,运行这个方法直到它完全完成,然后从中断的地方继续,现在知道了方法的结果”。
await
编辑: 我应该引用 Eric Lippert 在2011年10月 MSDN 杂志上的文章,因为这篇文章对我理解这些东西有很大的帮助。
有关加载更多信息和白页,请参见 给你。
我希望这能有所帮助。
SLaks 和 Killercam 的回答很好,我想我应该多加一点上下文。
您的第一个问题实质上是关于哪些方法可以被标记为 async。
async
标记为 async的方法可以返回 void、 Task或 Task<T>。它们之间的区别是什么?
可以等待返回异步方法的 Task<T>,当任务完成时,它将提供一个 T。
可以等待返回异步方法的 Task,并且当任务完成时,计划运行任务的继续。
不能等待返回异步方法的 void; 它是一个“触发并忘记”方法。它的确是异步工作的,而且您无法知道它何时完成。这非常奇怪; 正如 SLaks 所说,通常只有在创建异步事件处理程序时才会这样做。事件触发,处理程序执行; 没有人会“等待”事件处理程序返回的任务,因为事件处理程序不返回任务,即使他们这样做了,什么代码会使用任务的东西?首先,通常不是用户代码将控制权转移给处理程序。
你的第二个问题,在评论中,本质上是关于什么可以被 awaited:
什么样的方法可以 awaited? 返回空白的方法可以 awaited 吗?
不,不能等待返回空白的方法。编译器将 await M()转换为对 M().GetAwaiter()的调用,其中 GetAwaiter可能是实例方法或扩展方法。等待的值必须是您可以获得等待器的值; 显然,void-return 方法不会产生您可以获得等待器的值。
await M()
M().GetAwaiter()
GetAwaiter
返回方法可以产生可等待的值。我们预计第三方将希望创建他们自己的类 Task对象实现,这些对象可以等待,您将能够等待他们。但是,不允许声明返回除 void、 Task或 Task<T>以外的任何内容的 async方法。
(更新: 我的最后一句话可能会被 C # 的未来版本篡改; 有人建议允许异步方法的返回类型不是任务类型。)
(更新: 上面提到的特性进入了 C # 7。)
返回 Task和 Task<T>的方法是可组合的——这意味着您可以在 async方法内部对它们进行 await。
返回 void的 async方法是不可组合的,但它们确实具有另外两个重要属性:
当处理维护未完成异步操作的 计数的上下文时,第二点非常重要。
NET 上下文就是一个这样的上下文; 如果使用异步 Task方法而不等待异步 void方法,那么 ASP.NET 请求将过早完成。
另一个上下文是我为单元测试编写的 AsyncContext(可用的 给你)-AsyncContext.Run方法跟踪未完成的操作计数,当计数为零时返回。
AsyncContext
AsyncContext.Run