【问题标题】:What's the difference between returning void and returning a Task?返回 void 和返回 Task 有什么区别?
【发布时间】:2011-12-24 00:06:57
【问题描述】:

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

【问题讨论】:

    标签: c# asynchronous task-parallel-library return-type async-ctp


    【解决方案1】:

    SLaks 和 Killercam 的答案很好;我想我应该添加更多上下文。

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

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

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

    可以等待一个Task返回的async方法,当任务完成时,任务的延续被调度运行。

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

    在评论中,您的第二个问题本质上是关于 awaited 的内容:

    awaited 可以使用哪些方法? void返回方法可以awaited吗?

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

    Task-returning 方法可以产生等待值。我们预计第三方将希望创建自己的 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
    【解决方案2】:

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

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

    根据您的上述评论:

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


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

    有关加载更多信息和白页,请参阅here

    希望对你有所帮助。

    【讨论】:

      【解决方案3】:

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

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

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

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

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

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

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

        • 我认为也可以等待返回 void 类型的方法 - 你能详细说明一下吗?
        • 不,你不能。如果该方法返回void,您将无法获得它生成的任务。 (实际上,我什至不确定它是否会生成Task
        猜你喜欢
        • 1970-01-01
        • 2019-10-08
        • 2012-07-01
        • 2011-09-29
        • 1970-01-01
        • 1970-01-01
        • 2012-03-22
        • 2016-06-28
        • 2021-11-18
        相关资源
        最近更新 更多