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

时间:2012-08-27 14:33:40

标签: c# asynchronous .net-4.5

在什么情况下会想要使用

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));
}

7 个答案:

答案 0 :(得分:355)

1)通常情况下,您需要返回Task。当需要具有void返回类型(对于事件)时,主要的例外情况应该是。如果没有理由拒绝让调用者await成为你的任务,为什么不允许呢?

2)返回async的{​​{1}}方法在另一个方面很特殊:它们代表顶级异步操作,并且具有在您的任务中发挥作用的其他规则返回异常。最简单的方法是通过示例显示差异:

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的例外情况。当垃圾收集器来清理任务时,它会发现该任务导致异常,并且没有人处理该异常。发生这种情况时,g处理程序会运行。你永远不应该让这件事发生。要使用您的示例,

TaskScheduler.UnobservedTaskException

是的,在这里使用public static async void AsyncMethod2(int num) { await Task.Factory.StartNew(() => Thread.Sleep(num)); } async,如果抛出异常,它们会确保您的方法仍能正常工作。

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

答案 1 :(得分:36)

我在JérômeLaban写的关于asyncvoid的非常有用的文章中遇到过: https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html

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

  

这背后的原因是使用的同步上下文   AsyncVoidMethodBuilder,在此示例中为none。什么时候没有   环境同步上下文,任何未处理的异常   在ThreadPool上重新抛出异步void方法的主体。而   似乎没有其他合乎逻辑的地方那种未经处理的地方   异常可能被抛出,不幸的结果就是这个过程   正在被终止,因为ThreadPool上的未处理异常   从.NET 2.0开始有效地终止进程。你可以拦截   使用AppDomain.UnhandledException事件的所有未处理的异常,   但是没有办法从这个事件中恢复过程。

     

在编写UI事件处理程序时,async void方法是以某种方式   无痛,因为异常的处理方式与之相同   非异步方法;他们被扔在Dispatcher上。有一个   从这些例外中恢复的可能性,不仅仅是正确的   对于大多数情况。但是,在UI事件处理程序之外,async void   方法在某种程度上是危险的使用,可能不容易找到。

答案 2 :(得分:24)

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

  1. 异步void方法具有不同的错误处理语义。当异步任务或异步任务方法抛出异常时,将捕获该异常并将其放在Task对象上。使用async void方法,没有Task对象,因此异步void方法抛出的任何异常都将直接在SynchronizationContext上引发(SynchronizationContext表示可执行代码的位置“。)async void方法时处于活动状态开始
  2. 异步无效方法的异常无法通过Catch捕获

    with open('file.txt', 'r') as f:
        with open('file2.txt', 'w') as w:
            data = f.readlines()
            start, end = 0, 0
            for i in xrange(len(data)):
                if 'crypt' in data[i]:
                    start = i
                    break
            data2 = data[::-1]
            for j in xrange(len(data2)):
                if 'crypt' in data2[j]:
                    end = j
                    break
            for t in range(start, len(data)-end):
                w.write(data[t])
    

    使用AppDomain.UnhandledException或GUI / ASP.NET应用程序的类似catch-all事件可以观察到这些异常,但是使用这些事件进行常规异常处理是一种不可维护的方法(它会使应用程序崩溃)。

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

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

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

答案 3 :(得分:9)

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

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

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

在所有情况下,功能都会转换为一系列任务。区别在于函数返回什么。

     

在第一种情况下,该函数返回一个最终产生t的任务。

     

在第二种情况下,该函数返回一个没有积的任务,但是您可以   仍在等待它知道何时运行完成。

     

第三种情况令人讨厌。第三种情况类似于第二种情况,除了   您甚至都无法收回任务。你无法知道什么时候   该功能的任务已完成。

     

异步无效情况是“火灾和   “”:您启动了任务链,但是您不在乎它何时   完成。函数返回时,您所知道的就是   直到第一次等待执行。第一次等待后的一切   将来会在某个未指定的时间运行   访问。

答案 4 :(得分:5)

我认为你也可以使用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()); } } } 开始后台操作,只要你小心捕捉异常。想法?

{{1}}

答案 5 :(得分:0)

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

因此,如果该方法是异步的,则最好等待它,因为您可以失去异步优势。

答案 6 :(得分:-1)

According to Microsoft documentation,切勿使用async void

  

请勿执行以下操作:以下示例使用async void,   到达第一次等待时,HTTP请求完成:

     
      
  • 在ASP.NET Core应用程序中,这总是不好的做法。

  •   
  • 在HTTP请求完成后访问HttpResponse。

  •   
  • 使进程崩溃。

  •