12

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...

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • Couldn't you just encapsulate that into a method? Something like `AsyncTryCatch(async () => /* Some code that can throw an exception */, async ex => /* error handling */)`. – svick Aug 19 '13 at 09:42
  • Do you really need to show a message box? Perhaps something like a message panel where you print exceptions would do? – athabaska Aug 19 '13 at 09:44
  • @svick, yes, I thought of something like that, but it's still not very convenient; for instance, it doesn't support multiple catch clauses, and doesn't tell me whether the operation succeeded. – Thomas Levesque Aug 19 '13 at 09:56
  • 1
    @athabaska, no, that's not an option. Anyway, my question isn't limited to showing message boxes, it's more about find a workaround for the "no await in catch" rule. – Thomas Levesque Aug 19 '13 at 09:57
  • @ThomasLevesque Normal `try`-`catch` doesn't tell you whether the operation succeeded, but the method could by returning a `bool`. And multiple `catch` clauses could be done something like this: `AsyncTryCatch(async () => /* whatever */, async (SomeException ex) => /* handle SomeException */, async (AnotherException ex) => /* handle AnotherException */)`. Though you would need overloads for each number of `catch` clauses. – svick Aug 19 '13 at 11:44
  • I have used this type of solution before as well and I agree that it is ugly. In this very specific case, where it seems you want to wait for the dialog's result, why not simply invoke it as in: `catch (Exception ex) { var dialog = new MessageDialog("Something went wrong!"); var result = dialog.ShowAsync().GetResults(); }` – Alex Aug 21 '13 at 17:28
  • @Alex, because it doesn't work: the dialog is shown on the UI thread, which would be blocked on the call to GetResults (see [this answer](http://stackoverflow.com/a/11179035/98713)) – Thomas Levesque Aug 21 '13 at 18:53
  • Is that still true if you insert an intermediate step, as in: ` public static async Task DontBlockUi(Task asyncUiTask) { await asyncUiTask.ConfigureAwait(false); }` – Alex Aug 21 '13 at 19:13
  • @Alex, interesting idea, but no, it doesn't work (just tried it). It just blocks the UI forever. – Thomas Levesque Aug 21 '13 at 20:13

2 Answers2

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

Pedro.The.Kid
  • 1,968
  • 1
  • 14
  • 18
  • It's not really the same, because in this case the continuation is an `async void`, so it's not awaited; if I add some code after the ContinueWith call, it could run before the continuation is complete. – Thomas Levesque Aug 21 '13 at 15:06
  • Also, the syntax is still a mess... but I'm beginning to think there's no way around that. – Thomas Levesque Aug 21 '13 at 15:07
  • @ThomasLevesque after reading your comment I started to try and as i don't have a windows 8 machine can you try an see if dialog.ShowAsync().Wait(); is valid inside the catch – Pedro.The.Kid Aug 21 '13 at 15:47
  • Yes, it's valid (in the sense that it will compile), but it won't work, for the reason explained [here](http://stackoverflow.com/questions/11178764/messagedialog-task-not-shown-if-i-use-task-wait-instead-of-await/11179035#11179035) – Thomas Levesque Aug 21 '13 at 15:52
  • This won't work correctly. Your `ContinueWith()` returns a `Task` and to wait for that properly, you either need `await await`, or you need to use `Unwrap()`. – svick Aug 21 '13 at 19:22
  • @svick just because I didn't put the Unwrap at the end it doesn't make the answer wrong it was just a generic approach to the problem. – Pedro.The.Kid Aug 22 '13 at 13:26
0

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.

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758