Translating exceptions for Task<T> with Task Parallel Library a la await

依然范特西╮ 提交于 2019-12-08 12:35:39

问题


I'm trying to do the equivalent of the dollowing C# 5 pseudocode:-

async Task<int> CallAndTranslate()
{
    try 
    {
        return await client.CallAsync();
    } catch(FaultException ex) {
        if (ex.FaultCode ...)
            throw new Exception("translated");
    }
}

Given an arbitrary Task which does not return a result, translating exceptions when backporting from C# 5 is easy using the technique supplied by @Drew Marsh

This technique doesn't generalize trivially to Task<T> as any overload of Task.ContinueWith I can see returns a bald Task, not a Task<T>.

Is there a way to achieve this using the TPL APIs without having to resort to:

  • wrapping it in another Task<T>
  • causing the exception to go through the machinations of getting thrown and caught through the exception handling mechanisms
  • ADDED After intial answer.... should leave stack trace alone if exception is not to be translated

Here's my naive placeholder implementation:

public class TranslatingExceptions
{
    Task<int> ApiAsync()
    {
        return Task<int>.Factory.StartNew( () => { 
           throw new Exception( "Argument Null" ); } );
    }

    public Task<int> WrapsApiAsync() 
    {
        return ApiAsync().TranslateExceptions(x=>{
            if (x.Message == "Argument Null" )
                throw new ArgumentNullException();
        });
    }

    [Fact]
    public void Works()
    {
        var exception = Record.Exception( () =>
            WrapsApiAsync().Wait() );
        Assert.IsType<ArgumentNullException>( exception.InnerException );
    }
}

The following Task<T> extension implements my placeholder implementation:

static class TaskExtensions
{
    public static Task<T> TranslateExceptions<T>( this Task<T> task, Action<Exception> translator )
    {
    // TODO REPLACE NAIVE IMPLEMENTATION HERE
        return Task<T>.Factory.StartNew( () =>
        {
            try
            {
                return task.Result;
            }
            catch ( AggregateException exception )
            {
                translator( exception.InnerException );
                throw;
            }
        } );
    }
}

回答1:


You can imitate await in .NET 4.0 using iterators ( yield ), but it's not pretty.

Without a state machine, you're missing the whole point of await which is to return control to the caller until the work is complete and only then continue execution.

Um, maybe I've missed the point! If you just want to use a ContinueWith<T> it's just a slight tweak to the code from Drew Marsh:

public Task<int> ApiAsync() // The inner layer exposes it exactly this way
{
    return Task<int>.Factory.StartNew( () =>
        { throw new Exception( "Argument Null" ); } );
}

// this layer needs to expose it exactly this way
public Task<int> WrapsApiAsync()
{
    // Grab the task that performs the "original" work
    Task<int> apiAsyncTask = ApiAsync();

    // Hook a continuation to that task that will do the exception "translation"
    Task<int> result = apiAsyncTask.ContinueWith( antecedent =>
    {
        // Check if the antecedent faulted
        // If so check what the exception's message was
        if ( antecedent.IsFaulted )
        {
            if ( antecedent.Exception.InnerException.Message == "Argument Null" )
            {
                throw new ArgumentNullException();
            }

            throw antecedent.Exception.InnerException;
        }

        return antecedent.Result;
    },
    TaskContinuationOptions.ExecuteSynchronously );

    // Now we return the continuation Task from the wrapper method
    // so that the caller of the wrapper method waits on that
    return result;
}

UPDATE: sample using TaskCompletionSource

public static Task<int> WrapsApiAsync()
{
    var tcs = new TaskCompletionSource<int>();

    Task<int> apiAsyncTask = ApiAsync();

    apiAsyncTask.ContinueWith( t =>
        {
            switch ( t.Status )
            {
                case TaskStatus.RanToCompletion:
                    tcs.SetResult( task.Result );
                    break;

                case TaskStatus.Canceled:
                    tcs.SetCanceled();
                    break;

                case TaskStatus.Faulted:

                    if ( t.Exception.InnerException.Message == "Argument Null" )
                    {
                        try
                        {
                            throw new ArgumentNullException();
                        }
                        catch ( ArgumentNullException x )
                        {
                            tcs.SetException( x );
                        }
                    }
                    else
                    {
                        tcs.SetException( t.Exception.InnerException );
                    }

                    break;
            }
        }
    );

    return tcs.Task;
}


来源:https://stackoverflow.com/questions/8882211/translating-exceptions-for-taskt-with-task-parallel-library-a-la-await

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!