简体   繁体   中英

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 .

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 ?

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.

You use the void return type primarily to define event handlers, which require that return type. The caller of a void-returning async method can't await it and can't catch exceptions that the method throws.

This code will cause a crash, because the exception thrown from Exc doesn't get propagated back to 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 .

The reason you're seeing a crash is because DoWork is propagating that exception, and DoWork is an async void method. 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). As I describe in my MSDN article on best practices, strive to avoid 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. 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. 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 .

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(); } work for you?

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.

Note, that by doing any of that any potential gain from async IO is lost.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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