ChatGPT解决这个技术问题 Extra ChatGPT

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

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


E
Eric Lippert

SLaks 和 Killercam 的回答很好;我想我会添加更多的上下文。

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

标记为异步的方法可以返回 void、Task 或 Task。它们之间有什么区别?

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

可以等待返回的异步方法 Task,当任务完成时,任务的继续会被安排运行。

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

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

可以等待什么样的方法?可以等待返回无效的方法吗?

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

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

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

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


+1 我认为唯一缺少的是在返回 void 的异步方法中如何处理异常的差异。
@JamesCadd:假设一些异步工作引发异常。谁抓住它?启动异步任务的代码不再在堆栈上——它甚至可能不在同一个线程上——并且异常假设所有的 catch/finally 块都在堆栈上。所以你会怎么做?我们将异常信息存储在 Task 中,以便您稍后检查。但是,如果该方法返回无效,则用户代码没有可用的任务。我们究竟如何处理这种情况一直存在争议,我现在不记得我们做了什么决定。
实际上,我在 BUILD 向 Stephen Toub 提出了这个问题。在 .NET 4.0 中,一旦 TPL 检测到未观察到它们,Tasks 中未观察到的未处理异常最终会导致进程崩溃。在 4.5 中,他们更改了默认行为,以便仍会通过 TaskScheduler::UnobservedTaskException 事件报告未观察到的异常,但不会再使进程崩溃。如果您想要旧的 4.0 行为,您可以选择返回使用 。很可能所做的更改正是为了支持 void 异步方法的即发即弃。
async void 方法会在它们开始执行时处于活动状态的 SynchronizationContext 上引发异常。这类似于(同步)事件处理程序的行为。 @DrewMarsh:UnobservedTaskException 和运行时设置仅适用于“即发即弃”异步 Task 方法,不适用于 async void 方法。
异步异常处理信息的引用链接:blogs.msdn.com/b/pfxteam/archive/2012/04/12/10293335.aspx#11
S
SLaks

如果调用者想要等待任务或添加延续。

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


我认为等待返回 void 类型的方法也是可能的——你能详细说明一下吗?
不,你不能。如果该方法返回 void,则您无法获得它生成的任务。 (实际上,我不确定它是否甚至会生成 Task
S
Stephen Cleary

返回 TaskTask<T> 的方法是可组合的 - 这意味着您可以在 async 方法中await它们。

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

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

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

ASP.NET 上下文就是这样一种上下文。如果您使用 async Task 方法而不从 async void 方法等待它们,那么 ASP.NET 请求将过早完成。

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


M
MoonKnight

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

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

根据您上面的评论:

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

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

如需加载更多信息和白页,请参阅 here

我希望这会有所帮助。