使用任务并行库等待任务<t>的异常等待</t>

时间:2012-01-16 15:31:02

标签: .net asynchronous exception-handling task-parallel-library

我正在尝试做相当于下降的C#5伪代码: -

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从C#5向后移植很容易using the technique supplied by @Drew Marsh

这项技术不会琐碎地概括为Task<T>,因为我Task.ContinueWith的任何重载都会返回一个光头Task,而不是Task<T>

有没有办法使用TPL API实现这一点,而不必诉诸:

  • 将其包装在另一个Task<T>
  • 导致异常经历了被抛出并通过异常处理机制捕获的阴谋
  • ADDED初始回答后.... 如果不翻译异常,应单独留下堆栈跟踪

这是我天真的占位符实现:

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 );
    }
}

以下Task<T>扩展实现了我的占位符实现:

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 个答案:

答案 0 :(得分:2)

您可以使用迭代器(await)在.NET 4.0中模仿yield,但它并不漂亮。

如果没有状态机,你就会错过await的全部内容,即在工作完成之前将控制权返回给调用者,然后才继续执行。

嗯,也许错过了这一点!如果您只想使用ContinueWith<T>,只需稍微调整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;
}

更新:使用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;
}