首页 文章

async / await - 何时返回Task vs void?

提问于
浏览
360

在什么情况下人们想要使用

public async Task AsyncMethod(int num)

代替

public async void AsyncMethod(int num)

我能想到的唯一情况是,您是否需要能够跟踪其进度的任务 .

此外,在以下方法中,async和await关键字是否不必要?

public static async void AsyncMethod2(int num)
{
    await Task.Factory.StartNew(() => Thread.Sleep(num));
}

6 回答

  • 28

    我发现了这篇非常有用的关于 asyncvoid 的文章,由JérômeLaban撰写:http://www.jaylee.org/post/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.aspx

    底线是 async+void 可以使系统崩溃,通常只应在UI端事件处理程序上使用 .

    背后的原因是AsyncVoidMethodBuilder使用的同步上下文,在此示例中为none . 当没有环境同步上下文时,在ThreadPool上重新抛出异步void方法的主体未处理的任何异常 . 虽然似乎没有其他逻辑位置可以抛出那种未处理的异常,但不幸的结果是该进程被终止,因为ThreadPool上的未处理异常有效地终止了自.NET 2.0以来的进程 . 您可以使用AppDomain.UnhandledException事件拦截所有未处理的异常,但无法从此事件中恢复该进程 . 在编写UI事件处理程序时,异步void方法在某种程度上是无痛的,因为异常的处理方式与非异步方法相同;他们被抛在Dispatcher上 . 有可能从这些例外中恢复,对大多数情况来说都是正确的 . 但是,在UI事件处理程序之外,异步void方法在某种程度上是危险的,并且可能不容易找到 .

  • 4

    我从这些陈述中得到了明确的想法 .

    • 异步void方法具有不同的错误处理语义 . 当异步任务或异步任务方法抛出异常时,将捕获该异常并将其放在Task对象上 . 使用async void方法,没有Task对象,因此异步void方法抛出的任何异常都将直接在SynchronizationContext上生成(SynchronizationContext表示可能执行的位置"where"代码 . )当async void方法启动时处于活动状态

    Async Void方法的例外情况无法通过Catch捕获

    private async void ThrowExceptionAsync()
    {
      throw new InvalidOperationException();
    }
    public void AsyncVoidExceptions_CannotBeCaughtByCatch()
    {
      try
      {
        ThrowExceptionAsync();
      }
      catch (Exception)
      {
        // The exception is never caught here!
        throw;
      }
    }
    

    对于GUI / ASP.NET应用程序,可以使用AppDomain.UnhandledException或类似的catch-all事件来观察这些异常,但是使用这些事件进行常规异常处理是不可维护性的一个因素(它会导致应用程序崩溃) .

    • 异步void方法具有不同的组合语义 . 返回任务或任务的异步方法可以使用await,Task.WhenAny,Task.WhenAll等轻松编写 . 返回void的异步方法不提供通知调用代码已完成的简单方法 . 启动几个异步void方法很容易,但要确定它们何时完成并不容易 . Async void方法将在启动和结束时通知其SynchronizationContext,但自定义SynchronizationContext是常规应用程序代码的复杂解决方案 .

    • Async Void方法在使用同步事件处理程序时很有用,因为它们直接在SynchronizationContext上引发异常,这类似于同步事件处理程序的行为方式

    有关详细信息,请查看此链接https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

  • 3

    我认为你也可以使用 async void 开始后台操作,只要你小心捕捉异常 . 思考?

    class Program {
    
        static bool isFinished = false;
    
        static void Main(string[] args) {
    
            // Kick off the background operation and don't care about when it completes
            BackgroundWork();
    
            Console.WriteLine("Press enter when you're ready to stop the background operation.");
            Console.ReadLine();
            isFinished = true;
        }
    
        // Using async void to kickoff a background operation that nobody wants to be notified about when it completes.
        static async void BackgroundWork() {
            // It's important to catch exceptions so we don't crash the appliation.
            try {
                // This operation will end after ten interations or when the app closes. Whichever happens first.
                for (var count = 1; count <= 10 && !isFinished; count++) {
                    await Task.Delay(1000);
                    Console.WriteLine($"{count} seconds of work elapsed.");
                }
                Console.WriteLine("Background operation came to an end.");
            } catch (Exception x) {
                Console.WriteLine("Caught exception:");
                Console.WriteLine(x.ToString());
            }
        }
    }
    
  • 16

    1)通常,你想要返回一个 Task . 主要的例外应该是当你需要 void 返回类型(对于事件) . 如果没有理由不允许让你的呼叫工作,那么为什么不允许呢?

    2) async 返回 void 的方法在另一个方面是特殊的:它们代表顶级异步操作,并且在您的任务返回异常时有其他规则可以发挥作用 . 最简单的方法是显示差异是一个例子:

    static async void f()
    {
        await h();
    }
    
    static async Task g()
    {
        await h();
    }
    
    static async Task h()
    {
        throw new NotImplementedException();
    }
    
    private void button1_Click(object sender, EventArgs e)
    {
        f();
    }
    
    private void button2_Click(object sender, EventArgs e)
    {
        g();
    }
    
    private void button3_Click(object sender, EventArgs e)
    {
        GC.Collect();
    }
    

    f 的异常总是"observed" . 离开顶级异步方法的异常被简单地视为任何其他未处理的异常 . 从未观察到 g 的异常 . 当垃圾收集器来清理任务时,它会发现该任务导致异常,并且没有人处理该异常 . 发生这种情况时, TaskScheduler.UnobservedTaskException 处理程序会运行 . 你永远不应该让这种情况发生 . 要使用你的例子,

    public static async void AsyncMethod2(int num)
    {
        await Task.Factory.StartNew(() => Thread.Sleep(num));
    }
    

    是的,在这里使用 asyncawait ,如果抛出异常,它们会确保您的方法仍能正常工作 .

    有关更多信息,请参阅:http://msdn.microsoft.com/en-us/magazine/jj991977.aspx

  • 0

    我的答案很简单,你可以等待void方法错误CS4008无法等待'void'TestAsync e:\ test \ TestAsync \ TestAsync \ Program.cs

    因此,如果方法是异步的,那么最好等待,因为你可以放松异步优势 .

  • 323

    调用异步void的问题是你甚至没有得到任务,你无法知道函数的任务何时完成(参见https://blogs.msdn.microsoft.com/oldnewthing/20170720-00/?p=96655

    以下是调用异步函数的三种方法:

    async Task<T> SomethingAsync() { ... return t; }
    async Task SomethingAsync() { ... }
    async void SomethingAsync() { ... }
    

    在所有情况下,该功能都转换为一系列任务 . 不同之处在于函数返回的内容 . 在第一种情况下,函数返回最终产生t的任务 . 在第二种情况下,该函数返回一个没有产品的任务,但您仍然可以等待它知道它何时运行完成 . 第三种情况是令人讨厌的 . 第三种情况就像第二种情况,除了你甚至没有得到任务 . 您无法知道函数的任务何时完成 . async void case是一个“一劳永逸”:你启动任务链,但你不关心它什么时候结束 . 当函数返回时,你所知道的是第一个await的所有内容都已执行 . 第一次等待之后的所有内容将在未来的某个未指定的点运行,您无权访问 .

相关问题