返回 void 和返回 Task 有什么区别?

在查看各种 C # 异步 CTP 示例时,我看到了一些返回 void的异步函数,以及一些返回非通用 Task的异步函数。我可以理解为什么返回 Task<MyType>对于在异步操作完成时向调用者返回数据是有用的,但是我看到的返回类型为 Task的函数从来不返回任何数据。为什么不返回 void

43952 次浏览

如果调用方希望等待任务或添加继续。

事实上,返回 void的唯一原因是,如果 不能返回 Task,因为您正在编写一个事件处理程序。

类型 Task<T>是任务并行库(TPL)的主力类型,它表示“某些工作/作业将在未来产生类型 T的结果”的概念。“将在未来完成但不返回结果的工作”的概念由非泛型 Task 类型表示。

准确地说,T类型的结果将如何产生,以及特定任务的实现细节; 工作可能被外包到本地机器上的另一个进程、另一个线程等。TPL 任务通常从当前进程的线程池外包给工作线程,但是实现细节并不是 Task<T>类型的基础; 相反,Task<T>可以表示任何产生 T的高延迟操作。

根据你上面的评论:

await表达式意味着“计算这个表达式以获得一个对象,该对象表示将在未来产生结果的工作。将当前方法的其余部分注册为与该任务的继续相关联的回调。一旦这个任务生成并且回调被注册,马上就将控制权返回给我的调用者”。这与常规方法调用相反,常规方法调用的意思是“记住你在做什么,运行这个方法直到它完全完成,然后从中断的地方继续,现在知道了方法的结果”。


编辑: 我应该引用 Eric Lippert 在2011年10月 MSDN 杂志上的文章,因为这篇文章对我理解这些东西有很大的帮助。

有关加载更多信息和白页,请参见 给你

我希望这能有所帮助。

SLaks 和 Killercam 的回答很好,我想我应该多加一点上下文。

您的第一个问题实质上是关于哪些方法可以被标记为 async

标记为 async的方法可以返回 voidTaskTask<T>。它们之间的区别是什么?

可以等待返回异步方法的 Task<T>,当任务完成时,它将提供一个 T。

可以等待返回异步方法的 Task,并且当任务完成时,计划运行任务的继续。

不能等待返回异步方法的 void; 它是一个“触发并忘记”方法。它的确是异步工作的,而且您无法知道它何时完成。这非常奇怪; 正如 SLaks 所说,通常只有在创建异步事件处理程序时才会这样做。事件触发,处理程序执行; 没有人会“等待”事件处理程序返回的任务,因为事件处理程序不返回任务,即使他们这样做了,什么代码会使用任务的东西?首先,通常不是用户代码将控制权转移给处理程序。

你的第二个问题,在评论中,本质上是关于什么可以被 awaited:

什么样的方法可以 awaited? 返回空白的方法可以 awaited 吗?

不,不能等待返回空白的方法。编译器将 await M()转换为对 M().GetAwaiter()的调用,其中 GetAwaiter可能是实例方法或扩展方法。等待的值必须是您可以获得等待器的值; 显然,void-return 方法不会产生您可以获得等待器的值。

返回方法可以产生可等待的值。我们预计第三方将希望创建他们自己的类 Task对象实现,这些对象可以等待,您将能够等待他们。但是,不允许声明返回除 voidTaskTask<T>以外的任何内容的 async方法。

(更新: 我的最后一句话可能会被 C # 的未来版本篡改; 有人建议允许异步方法的返回类型不是任务类型。)

(更新: 上面提到的特性进入了 C # 7。)

返回 TaskTask<T>的方法是可组合的——这意味着您可以在 async方法内部对它们进行 await

返回 voidasync方法是不可组合的,但它们确实具有另外两个重要属性:

  1. 它们可以用作事件处理程序。
  2. 它们表示“顶级”异步操作。

当处理维护未完成异步操作的 计数的上下文时,第二点非常重要。

NET 上下文就是一个这样的上下文; 如果使用异步 Task方法而不等待异步 void方法,那么 ASP.NET 请求将过早完成。

另一个上下文是我为单元测试编写的 AsyncContext(可用的 给你)-AsyncContext.Run方法跟踪未完成的操作计数,当计数为零时返回。