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