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


128

在查看各种C#异步CTP示例时,我看到一些返回的异步函数void,以及其他一些返回非泛型的函数Task。我可以看到为什么Task<MyType>异步操作完成后,返回a 有助于将数据返回给调用方,但是我看到的具有返回类型的函数Task从不返回任何数据。为什么不回来void

Answers:


214

Slaks和Killercam的答案很好。我以为我会添加更多上下文。

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

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

一个Task<T>返回异步方法可以等待,并在任务完成时,它会毫无顾忌了一个T.

一个Task返回异步方法可以等待,而当任务完成时,任务的延续计划运行。

void返回异步方法不能等待; 这是一种“解雇”的方法。它确实是异步工作的,您无法知道何时完成。这有点奇怪。正如SLaks所说,通常只有在创建异步事件处理程序时才这样做。事件触发,处理程序执行;没有人会“等待”事件处理程序返回的任务,因为事件处理程序不会返回任务,即使它们返回了,什么代码也可以将任务用于某些任务?首先,通常不是用户代码将控制权转移到处理程序。

在评论中,您的第二个问题实质上是关于可以await编辑的内容:

可以采用哪种方法await?可以使用返回空洞的方法await吗?

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

Task-返回方法可以产生等待的值。我们期望第三方将希望创建自己的Task可以等待的对象的实现,而您将能够等待它们。然而,你将不会被允许申报async方法是什么回报,但是voidTask还是Task<T>

(更新:我的最后一句话可能被C#的未来版本所伪造;有人建议允许异步方法的任务类型以外的返回类型。)

(更新:上面提到的功能使它进入了C#7。)


7
+1我认为唯一缺少的是在返回void的异步方法中如何处理异常的差异。
若昂·安杰洛

10
@JamesCadd:假设一些异步工作抛出异常。谁抓到了?启动异步任务的代码不再在堆栈上-甚至可能不在同一线程上 -并且例外情况假定所有catch / finally块都在堆栈上。所以你会怎么做?我们将异常信息存储在任务中,以便您以后可以检查它。但是,如果返回的方法无效,则用户代码没有可用的任务。我们如何确切地处理这种情况一直存在一些争议,我现在不记得我们所决定的内容。
埃里克·利珀特

8
我实际上在BUILD询问了Stephen Toub的问题。在.NET 4.0中,一旦TPL检测到未观察到的异常,Tasks中未处理的异常最终将导致进程崩溃。在4.5中,他们更改了默认行为,以便仍可通过TaskScheduler :: UnobservedTaskException事件报告未观察到的异常,但不会使进程崩溃。如果您希望使用旧的4.0行为,则可以使用<runtime> <ThrowUnobservedTaskExceptions enabled =“ true” /> </ runtime>重新选择加入。所做的更改很可能正是为了支持即弃即弃的无效异步方法而进行的。
德鲁·马什

4
async void方法SynchronizationContext在开始执行时就处于活动状态,从而引发异常。这类似于(同步)事件处理程序的行为。@DrewMarsh:UnobservedTaskException和运行时设置仅适用于“解雇”异步Task方法,而不适用于async void方法。
Stephen Cleary


23

如果呼叫者想等待任务或添加继续。

实际上,返回的唯一原因void是由于编写事件处理程序而无法返回Task


我认为也可以等待返回void类型的方法-您能详细说明一下吗?
James Cadd

1
不,你不能。如果该方法返回void,则您将无法执行其生成的任务。(其实,我不知道是否即使产生Task的话)
SLaks

18

方法返回Task,并Task<T>是可组合的-这意味着你可以await在他们里面的的async方法。

async返回void的方法是不可组合的,但是它们还有两个其他重要属性:

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

当你处理与保持上下文的第二点是很重要的计数的异步操作的。

ASP.NET上下文就是这样一种上下文。如果使用异步Task方法而没有从异步方法等待它们void,则ASP.NET请求将太早完成。

另一个上下文是AsyncContext我为单元测试编写的(可在此处获得)-该AsyncContext.Run方法跟踪未完成的操作计数,并在操作计数为零时返回。


12

类型Task<T>是任务并行库(TPL)的主力类型,它表示“ T将来会产生类型结果的某些工作/工作”的概念。非通用Task类型表示“将来会完成但没有结果的工作”的概念。

精确地将如何产生类型的结果T是特定任务的实现细节;TPL任务通常从当前进程中的线程池中移植到工作线程中,但是实现细节不是该Task<T>类型的基础;TPL任务通常被移植到本地计算机上的另一个进程,另一个线程等。而是Task<T>可以表示产生的任何高延迟操作T

根据您上面的评论:

await表达式的意思是“评估该表达式以获得代表将来将要产生结果的工作的对象。将当前方法的其余部分注册为与该任务的继续相关的回调。一旦产生了该任务并进行了回调已注册,请立即将控制权返还给我的呼叫者”。这与常规方法调用相反/相反,常规方法调用的意思是“记住您在做什么,运行此方法直到其完全完成,然后从您中断的地方开始,现在就知道该方法的结果”。


编辑:我应该在2011年10月的《 MSDN杂志》中引用Eric Lippert的文章,因为这对我一开始就了解这些东西有很大帮助。

有关负载的更多信息和白页,请参见此处

我希望这会有所帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.