简体   繁体   English

如何将从异步函数引发的异常传播到正在等待的异步void函数?

[英]How can I propagate an exception thrown from an asynchronous function to an awaiting async void function?

I have a long chain of calls that eventually calls an asynchronous function in another assembly. 我有一长串的调用,这些调用最终会在另一个程序集中调用异步函数。 I want this function to be executed synchronously, and it may throw an exception, which I want to propagate up the call chain. 我希望此函数同步执行,并且可能会引发异常,该异常要在调用链中传播。

This scenario is minimally reproduced by the following snippet: 下面的代码片段最小化了这种情况:

static Task<int> Exc()
{
    throw new ArgumentException("Exc");
    return Task.FromResult(1);
}

static async void DoWork()
{
    await Exc();
}

static void Main(string[] args)
{
    try
    {
        DoWork();
    }
    catch (Exception e)
    {
        Console.WriteLine("Caught {0}", e.Message);
    }
}

This code will cause a crash, because the exception thrown from Exc doesn't get propagated back to Main . 此代码将导致崩溃,因为从Exc引发的异常不会传播回Main

Is there any way for me to have the exception thrown from Exc be handled by the catch block in Main , without changing every function in my call chain to use async , await , and return a Task ? 我有什么办法可以让Main的catch块处理从Exc引发的异常,而无需更改调用链中的每个函数以使用asyncawait和返回Task

In my actual code, this asynchronous function is called from a very deep chain of function calls, all of which should executed synchronously. 在我的实际代码中,此异步函数是从非常深层的函数调用链中调用的,所有这些函数调用应同步执行。 Making them all asynchronous when they'll never be used (nor can they be safely used) asynchronously seems like a terrible idea, and I'd like to avoid it, if possible. 当它们永远不会被使用(也不能安全地使用)时,使它们全部异步似乎是一个糟糕的主意,如果可能的话,我想避免这样做。

According to MSDN , no. 根据MSDN ,没有。

You use the void return type primarily to define event handlers, which require that return type. 您主要使用void返回类型来定义需要该返回类型的事件处理程序。 The caller of a void-returning async method can't await it and can't catch exceptions that the method throws. 返回void的异步方法的调用者无法等待它,也无法捕获该方法引发的异常。

This code will cause a crash, because the exception thrown from Exc doesn't get propagated back to DoWork. 此代码将导致崩溃,因为从Exc引发的异常不会传播回DoWork。

Not at all. 一点也不。 The exception from Exc is being passed to DoWork and can be caught there if you have a try / catch in DoWork . 来自Exc的异常将传递给DoWork ,如果您在DoWork进行了try / catch ,则可以在其中捕获该异常。

The reason you're seeing a crash is because DoWork is propagating that exception, and DoWork is an async void method. 您看到崩溃的原因是因为DoWork正在传播该异常,而DoWork是一个async void方法。 This can be easily avoided by making DoWork an async Task method instead (note that async void should only be used for event handlers, which DoWork is clearly not). 可以通过使DoWork成为async Task方法来轻松避免这种情况(请注意, async void 应用于事件处理程序,而DoWork显然不是)。 As I describe in my MSDN article on best practices, strive to avoid async void . 正如我在有关最佳做法的MSDN文章中所描述的那样,努力避免async void

In my actual code, this asynchronous function is called from a very deep chain of function calls, all of which should executed synchronously. 在我的实际代码中,此异步函数是从非常深层的函数调用链中调用的,所有这些函数调用应同步执行。 Making them all asynchronous when they'll never be used (nor can they be safely used) asynchronously seems like a terrible idea, and I'd like to avoid it, if possible. 当它们永远不会被使用(也不能安全地使用)时,使它们全部异步似乎是一个糟糕的主意,如果可能的话,我想避免这样做。

The operation is either asynchronous or it is is not. 该操作要么是异步的,要么不是。 Since the low-level API you're calling is asynchronous, then it's best if your code consumes it asynchronously. 由于您正在调用的低级API是异步的,因此最好是代码以异步方式使用它。 Trying to wrap asynchronous code in synchronous code is extremely error-prone and a terrible idea. 尝试将异步代码包装在同步代码中非常容易出错,这是一个可怕的想法。 Though, sometimes, it is necessary. 虽然有时有必要。

So, the cleanest solution is to make asynchronous methods have asynchronous signatures. 因此, 最干净的解决方案是使异步方法具有异步签名。 And yes, that means going " async all the way ", as I describe in my MSDN article on async best practices. 是的,这意味着要“一直保持异步 ”,正如我在有关异步最佳实践的MSDN文章中所述。 However, if you prefer to do sync-over-async, then you can choose one of a variety of hacks that I describe in my recent "Brownfield async" article . 但是,如果您喜欢通过异步进行同步,那么可以选择我在我最近的“ Brownfield async”文章中描述各种黑客之一

I will provide a counter point to Stephens answer in case you insist on not making the entire stack async: 如果您坚持不让整个堆栈异步,那么我将提供斯蒂芬斯答案的反驳:

Would void DoWork() { Exec().Wait(); } 会使DoWork() { Exec().Wait(); } DoWork() { Exec().Wait(); } work for you? DoWork() { Exec().Wait(); }为您工作?

There are no async deadlocks in a console app. 控制台应用程序中没有异步死锁。 If this is not a console app you can use what Ivan linked to , or use ConfigureAwait(false) , or use Task.Run(...).Wait() which does not deadlock because the task body has no sync context. 如果这不是控制台应用程序,则可以使用Ivan链接到的应用程序,或者使用ConfigureAwait(false)或使用Task.Run(...).Wait()不会死锁,因为任务主体没有同步上下文。

Note, that by doing any of that any potential gain from async IO is lost. 请注意,通过执行任何上述操作,异步IO的任何潜在收益都将丢失。

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

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