Pattern for implementing sync methods in terms of

2019-02-09 20:40发布

问题:

I have an Async method returning a Task.

I also wish to offer a synchronous equivalent, but I don't want consumers of it to have to go unpacking AggregateExceptions.

Now I understand that the whole idea is that you couldn't arbitrarily pick one in a general way, and I know I could go read loads more Stephen Toub articles (I will, but not now) and I'll understand it all and can decide for myself.

In the interim, I want to use the fact that my tasks are actually just chained 'workflows' without parallelism, just intervening Waits (no, not TPL DataFlow) which shouldnt not result in more than one exception. In that case, would it be appropriate to handle as follows:

    CallAsync().Wait();
}
catch( AggregateException ae)
{
    throw ae.Flatten().First()

or am I guaranteed that an AggregateException always has an InnerException even if there are more than one. Or is there a case where I should fall back to .Flatten().First() ?


In some TPL docs, I see a reference to an Unwrap() method on AggregateException (not sure if it was an extension or something in a beta release).

As a placeholder, I'm doing:

void Call( )
{
    try
    {
        CallAsync().Wait();
    }
    catch ( AggregateException ex )
    {
        var translated = ex.InnerException ?? ex.Flatten().InnerExceptions.First();
        if ( translated == null )
            throw;
        throw translated;                 }
}

Task CallAsync(){ ...

回答1:

There's no "clean" way to do this that I know of. You can't use throw someInnerException; because you'll lose the stack wherever the exception originated in the the async workflow and if you just use throw; you're obviously going to propagate the AggregateException. What you would have to do for the synchronous method is have some kind of "wrapper" exception that you can stuff the first exception of the AggregateException into and then throw that consistently from the synchronous version of the method.

void Call()
{
    try
    {
        CallAsync().Wait();
    }
    catch (AggregateException ex)
    {
        throw new MyConsistentWrapperException("An exception occurred while executing my workflow. Check the inner exception for more details.", ex.Flatten().InnerExceptions.First());
    }
}

FWIW, they've solved this in 4.5 with the new ExceptionDispatchInfo class which will help you marshal exceptions across threads without whacking the stack. Then you could write the synchronous version like this:

void Call()
{
    try
    {
        CallAsync().Wait();
    }
    catch (AggregateException ex)
    {
        ExceptionDispatchInfo.Capture(ex.Flatten().InnerExceptions.First()).Throw();
    }
}