繁体   English   中英

async/await - 何时返回任务 vs 无效?

[英]async/await - when to return a Task vs void?

在什么场景下想要使用

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));
}
  1. 通常,您会想要返回一个Task 主要的例外应该是你需要一个void返回类型(对于事件)。 如果没有理由禁止调用者await您的任务,为什么要禁止它?

  2. 返回void async方法在另一方面是特殊的:它们代表顶级异步操作,并且在您的任务返回异常时具有额外的规则。 最简单的方法是用一个例子来说明差异:

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

是的, await这里使用asyncawait ,它们确保您的方法在抛出异常时仍然正常工作。

有关更多信息,请参阅: https : //docs.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming

我看到了 Jérôme Laban 写的关于asyncvoid这篇非常有用的文章: https : //jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async -void.html

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

这背后的原因是 AsyncVoidMethodBuilder 使用的同步上下文,在这个例子中没有。 当没有环境同步上下文时,异步 void 方法主体未处理的任何异常都会在 ThreadPool 上重新抛出。 虽然似乎没有其他合乎逻辑的地方可以抛出这种未处理的异常,但不幸的结果是进程被终止,因为自 .NET 2.0 以来,线程池上的未处理异常有效地终止了进程。 您可以使用 AppDomain.UnhandledException 事件拦截所有未处理的异常,但无法从该事件中恢复进程。

在编写 UI 事件处理程序时,异步 void 方法在某种程度上是无痛的,因为异常的处理方式与非异步方法中的处理方式相同; 它们被扔到调度器上。 有可能从此类异常中恢复,在大多数情况下是正确的。 然而,在 UI 事件处理程序之外,异步 void 方法使用起来有些危险,并且可能不容易找到。

调用 async void 的问题在于

你甚至没有拿回任务。 您无法知道函数的任务何时完成。 ——异步和等待的速成课程| 旧的新事物

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

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

在所有情况下,该功能都被转换为一系列任务。 不同之处在于函数返回的内容。

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

在第二种情况下,该函数返回一个没有产品的任务,但您仍然可以等待它以了解它何时运行完成。

第三种情况是令人讨厌的。 第三种情况与第二种情况类似,只是您甚至没有取回任务。 您无法知道函数的任务何时完成。

异步无效的情况是“即发即忘”:您启动任务链,但您不关心它何时完成。 当函数返回时,您所知道的就是直到第一个 await 为止的所有内容都已执行。 第一次 await 之后的所有内容都将在未来某个您无法访问的未指定点运行。

我从这句话中得到了清晰的主意。

  1. 异步void方法具有不同的错误处理语义。 从异步Task或异步Task方法抛出异常时,将捕获该异常并将其放置在Task对象上。 使用异步void方法时,没有Task对象,因此从异步void方法抛出的任何异常都将直接在SynchronizationContext(SynchronizationContext表示可能在其中执行代码的位置)上引发。开始了

异步无效方法的异常无法捕获

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

可以使用AppDomain.UnhandledException或类似的GUI / ASP.NET应用程序的全部捕获事件来观察这些异常,但是使用这些事件进行常规异常处理是导致无法维护的秘诀(会使应用程序崩溃)。

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

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

有关更多详细信息,请检查此链接https://msdn.microsoft.com/zh-cn/magazine/jj991977.aspx

我认为你也可以使用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());
        }
    }
}

根据 Microsoft 文档,永远不要使用async void

不要这样做:以下示例使用async void使 HTTP 请求在到达第一个等待时完成:

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

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

  • 使进程崩溃。

我的回答很简单,你不能等待 void 方法

Error   CS4008  Cannot await 'void' TestAsync   e:\test\TestAsync\TestAsyncProgram.cs

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM