96

I need to call an async method in a catch block before throwing again the exception (with its stack trace) like this :

try
{
    // Do something
}
catch
{
    // <- Clean things here with async methods
    throw;
}

But unfortunately you can't use await in a catch or finally block. I learned it's because the compiler doesn't have any way to go back in a catch block to execute what is after your await instruction or something like that...

I tried to use Task.Wait() to replace await and I got a deadlock. I searched on the Web how I could avoid this and found this site.

Since I can't change the async methods nor do I know if they use ConfigureAwait(false), I created these methods which take a Func<Task> that starts an async method once we are on a different thread (to avoid a deadlock) and waits for its completion:

public static void AwaitTaskSync(Func<Task> action)
{
    Task.Run(async () => await action().ConfigureAwait(false)).Wait();
}

public static TResult AwaitTaskSync<TResult>(Func<Task<TResult>> action)
{
    return Task.Run(async () => await action().ConfigureAwait(false)).Result;
}

public static void AwaitSync(Func<IAsyncAction> action)
{
    AwaitTaskSync(() => action().AsTask());
}

public static TResult AwaitSync<TResult>(Func<IAsyncOperation<TResult>> action)
{
    return AwaitTaskSync(() => action().AsTask());
}

So my questions is: Do you think this code is okay?

Of course, if you have some enhancements or know a better approach, I'm listening! :)

Adi Lester
  • 24,731
  • 12
  • 95
  • 110
user2397050
  • 963
  • 1
  • 7
  • 4
  • 2
    Using `await` in a catch block is actually allowed since C# 6.0 (see my answer below) – Adi Lester Nov 23 '14 at 11:13
  • 3
    Related **C# 5.0** error messages: ***CS1985***: *Cannot await in the body of a catch clause.* ***CS1984***: *Cannot await in the body of a finally clause.* – DavidRR May 17 '16 at 12:42

4 Answers4

178

You can move the logic outside of the catch block and rethrow the exception after, if needed, by using ExceptionDispatchInfo.

static async Task f()
{
    ExceptionDispatchInfo capturedException = null;
    try
    {
        await TaskThatFails();
    }
    catch (MyException ex)
    {
        capturedException = ExceptionDispatchInfo.Capture(ex);
    }

    if (capturedException != null)
    {
        await ExceptionHandler();

        capturedException.Throw();
    }
}

This way, when the caller inspects the exception's StackTrace property, it still records where inside TaskThatFails it was thrown.

Sam Harwell
  • 97,721
  • 20
  • 209
  • 280
57

You should know that since C# 6.0, it's possible to use await in catch and finally blocks, so you could in fact do this:

try
{
    // Do something
}
catch (Exception ex)
{
    await DoCleanupAsync();
    throw;
}

The new C# 6.0 features, including the one I just mentioned are listed here or as a video here.

Adi Lester
  • 24,731
  • 12
  • 95
  • 110
  • Support for [await in catch/finally blocks](https://en.wikipedia.org/wiki/C_Sharp_%28programming_language%29#C.23_6.0) in C# 6.0 is also indicated on Wikipedia. – DavidRR May 17 '16 at 12:47
  • 5
    @DavidRR. the Wikipedia is not authoritive. It is just another web site among millions as far as this is concerned. – Sam Hobbs Mar 28 '17 at 22:15
  • While this is valid for C# 6, the question was tagged C# 5 from the very beginning. This makes me wonder if having this answer here is confusing, or if we should just remove the specific version tag in these cases. – julealgon Dec 26 '18 at 22:32
16

If you need to use async error handlers, I'd recommend something like this:

Exception exception = null;
try
{
  ...
}
catch (Exception ex)
{
  exception = ex;
}

if (exception != null)
{
  ...
}

The problem with synchronously blocking on async code (regardless of what thread it's running on) is that you're synchronously blocking. In most scenarios, it's better to use await.

Update: Since you need to rethrow, you can use ExceptionDispatchInfo.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 1
    Thank you but unfortunately I know this method already. That's what I do normally, but I can't do this here. If I simply use `throw exception;` in the `if` statement the stack trace will be lost. – user2397050 May 18 '13 at 16:29
3

We extracted hvd's great answer to the following reusable utility class in our project:

public static class TryWithAwaitInCatch
{
    public static async Task ExecuteAndHandleErrorAsync(Func<Task> actionAsync,
        Func<Exception, Task<bool>> errorHandlerAsync)
    {
        ExceptionDispatchInfo capturedException = null;
        try
        {
            await actionAsync().ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            capturedException = ExceptionDispatchInfo.Capture(ex);
        }

        if (capturedException != null)
        {
            bool needsThrow = await errorHandlerAsync(capturedException.SourceException).ConfigureAwait(false);
            if (needsThrow)
            {
                capturedException.Throw();
            }
        }
    }
}

One would use it as follows:

    public async Task OnDoSomething()
    {
        await TryWithAwaitInCatch.ExecuteAndHandleErrorAsync(
            async () => await DoSomethingAsync(),
            async (ex) => { await ShowMessageAsync("Error: " + ex.Message); return false; }
        );
    }

Feel free to improve the naming, we kept it intentionally verbose. Note that there is no need to capture the context inside the wrapper as it is already captured in the call site, hence ConfigureAwait(false).

Community
  • 1
  • 1
mrts
  • 16,697
  • 8
  • 89
  • 72