Better way to show error messages in async methods

2020-07-03 07:21发布

问题:

The fact that we can't use the await keyword in catch blocks makes it quite awkward to show error messages from async methods in WinRT, since the MessageDialog API is asynchronous. Ideally I would like be able to write this:

    private async Task DoSomethingAsync()
    {
        try
        {
            // Some code that can throw an exception
            ...
        }
        catch (Exception ex)
        {
            var dialog = new MessageDialog("Something went wrong!");
            await dialog.ShowAsync();
        }
    }

But instead I have to write it like this:

    private async Task DoSomethingAsync()
    {
        bool error = false;
        try
        {
            // Some code that can throw an exception
            ...
        }
        catch (Exception ex)
        {
            error = true;
        }

        if (error)
        {
            var dialog = new MessageDialog("Something went wrong!");
            await dialog.ShowAsync();
        }
    }

All methods that need to do this have to follow a similar pattern, which I really don't like, because it reduces the code readability.

Is there a better way to handle this?


EDIT: I came up with this (which is similar to what svick suggested in his comments):

static class Async
{
    public static async Task Try(Func<Task> asyncAction)
    {
        await asyncAction();
    }

    public static async Task Catch<TException>(this Task task, Func<TException, Task> handleExceptionAsync, bool rethrow = false)
        where TException : Exception
    {
        TException exception = null;
        try
        {           
            await task;
        }
        catch (TException ex)
        {
            exception = ex;
        }

        if (exception != null)
        {
            await handleExceptionAsync(exception);
            if (rethrow)
                ExceptionDispatchInfo.Capture(exception).Throw();
        }
    }
}

Usage:

private async Task DoSomethingAsync()
{
    await Async.Try(async () => 
    {
        // Some code that can throw an exception
        ...
    })
    .Catch<Exception>(async ex =>
    {
        var dialog = new MessageDialog("Something went wrong!");
        await dialog.ShowAsync();
    });
}

.Catch<...> calls can be chained to mimick multiple catch blocks.

But I'm not really happy with this solution; the syntax is even more awkward than before...

回答1:

you already have that functionality in TPL

        await Task.Run(async () =>
        {
            // Some code that can throw an exception
            ...
        }).ContinueWith(async (a) =>
        {
            if (a.IsFaulted)
            {
                var dialog = new MessageDialog("Something went wrong!\nError: "
                           + a.Exception.Message);
                await dialog.ShowAsync();
            }
            else
            {
                var dialog2 = new MessageDialog("Everything is OK: " + a.Result);
                await dialog2.ShowAsync();
            }
        }).Unwrap();

In this machine I don't have Windows 8 so I tested in Windows 7 but I think is the same. *Edit as stated in the comments its needed .Unwrap(); in the end for the await to work



回答2:

C# 6 now supports await in catch and finally, so the code can be written the way I wanted it; a workaround is no longer needed.