6

I have an async method in a .NET 4.5 C# component:

public async Task<T> GetResultAsync()
{
    return PerformOperationAsync();
}

If PerformOperationAsync throws an exception, then I can catch an AggregateException on a client side, unwrap it and get the original exception thrown.

However, if I have slightly more complicated code:

public async Task<T> GetResultAsync()
{
    return PerformOperationAsync().ContinueWith(x =>
    {
        var result = x.Result;
        return DoSomethingWithResult(result);
    }, cancellationToken);
}

... then in case an exception occurs, the client catches a nested AggregateException, so it has to flatten it prior getting the original one.

Should this behavior be avoided or does client have to expect possibly nested AggregateException and call Flatten to unwrap all its levels? And if this behavior should be avoided by the component developer, then what's the right way to deal with it in ContinueWith scenario? I have plenty of similar situations, so I am trying to find the most lightweight method of handling them.

4
  • 1
    Do you really want to deal with manual continuations? They are a huge pain (although still awesome compared to callbacks etc.). Even if you're on .NET 4.0, you can use the async targeting pack to use await instead. Commented Apr 14, 2015 at 7:03
  • Of course I don't want to deal with manual continuations unless absolutely necessary. But there are cases I have to and I wonder how to properly handle AggregateException in such case. Commented Apr 14, 2015 at 7:45
  • Well, in general, you can just follow the usual rules for handling and throwing exceptions. Should the topmost caller be even aware of what's going two layers of indirection away? Should it expect e.g. an IOException, or some MySpecificException? For example, Microsoft Orleans considers each indirection a separate scope for aggregate exception, so each asynchronous call is a new layer in the resulting aggregate exception. Does that make sense for your case? It really is just basic system / API architecture, it's not specific to tasks or aggregate exceptions :) Commented Apr 14, 2015 at 7:50
  • Thanks @Luaan, this makes sense. And it in fact took just a short time to revise my code to stop using continuation and just use await/async. Works much smoother now. Commented Apr 14, 2015 at 8:10

2 Answers 2

5

C#5 async/await will help you deal with continuations and proper exception handling while simplifying the code.

public async Task<T> GetResultAsync()
{
    var result = await PerformOperationAsync().ConfigureAwait(false);
    return DoSomethingWithResult(result);
}

Your method is already marked as async, is it intended ?


To keep the continuation you can provide a TaskContinuationOptions with OnlyOnRanToCompletion value :

PerformOperationAsync().ContinueWith(x =>
{
    var result = x.Result;
    return DoSomethingWithResult(result);
}, TaskContinuationOptions.OnlyOnRanToCompletion);

or use the awaiter to raise the original exception

PerformOperationAsync().ContinueWith(x =>
{
    var result = x.GetAwaiter().GetResult();
    return DoSomethingWithResult(result);
}, cancellationToken);
Sign up to request clarification or add additional context in comments.

3 Comments

I thinks I have oversimplified this example. In fact there is more logic involved in continuation labmda, so I can't just get rid of ContinueWith clause.
Thanks. But actually your first suggestion made me revise my code and you were right: I was able to rewrite it just to use await, get rid of continuations and nested exceptions.
TaskContinuationOptions.OnlyOnRanToCompletion will actually make the problem worse: If PerformOperationAsync throws an exception, it will swallowed, and instead a TaskCanceledException will be thrown.
-1

In addition to Guillaume's answer, I am using a little extension method

public static class Ext
{
    /// <summary>
    /// Wait for task synchronously, then return result. Avoid AggregateExceptions 
    /// as it would be generated by asyncTask.Result.
    /// </summary>
    public static T SyncResult<T>(this Task<T> asyncTask) 
                                    => asyncTask.GetAwaiter().GetResult();
}

Instead of var result = x.Result;, use it like this:

var result = x.SyncResult();

It does the same as Guillaume explained, but is shorter to use and easier to memorize.

2 Comments

Upvoted. I have created an identically implemented method for my own use, and I have named it Wait2. I don't like this name, but IMHO the SyncResult is not much better either. The native Task.Result is not less synchronous than this method!
@TheodorZoulias - Yes, both are synchronous, but as I understood from the comments and from Guillaume's answer is that .GetAwaiter().GetResult() handles exceptions better than .Result does - and I bet you have seen those AggregateExceptions which you want to avoid: It is difficult to find out the root cause with those. If one finds a better method name than SyncResult, please let me know ;-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.