【问题标题】:Translating exceptions for Task<T> with Task Parallel Library a la await使用任务并行库翻译 Task<T> 的异常等待
【发布时间】:2012-02-11 12:34:44
【问题描述】:

我正在尝试做相当于 dollowing 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&lt;T&gt;,因为Task.ContinueWith 的任何重载我可以看到返回一个秃头Task,而不是Task&lt;T&gt;

有没有办法使用 TPL API 来实现这一点而无需求助于:

  • 将其包装在另一个 Task&lt;T&gt;
  • 导致异常通过异常处理机制被抛出和捕获
  • 在初始回答后添加......如果不翻译异常,则应单独保留堆栈跟踪

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

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&lt;T&gt; 扩展实现了我的占位符实现:

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

【问题讨论】:

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


    【解决方案1】:

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

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

    嗯,也许没有抓住重点!如果您只想使用ContinueWith&lt;T&gt;,只需对 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;
    }
    

    【讨论】:

    • 感谢您的回复。不幸的是,正如问题中所述,**Task**.ContinueWith 不返回 Task&lt;int&gt; 是这里的问题,因此提出这个问题(并且毫不奇怪地进行转换也不起作用)。也许,有人有一个 ToTaskOf 转换器?无论哪种方式,这都不会通过提供的测试平台,对不起! (我想要复制的不是状态机,而是Task&lt;T&gt; 杂耍)。我不记得去年@Jon Skeet 出色的“在 C# 5 中破坏异步”系列的内容。
    • MSDN 没有显示ContinueWith 的所有重载,但肯定有返回Task&lt;T&gt; 的重载。 This one 最接近,但在 .NET 4 RTM 中也有一个以 Task&lt;T&gt; 作为先行词。
    • 正确。这并不明显,但您只需从 continuation 方法返回一个结果,编译器将选择适当的重载,从而导致 Task.
    • 谢谢两位。啊,抱歉最初怀疑你(我错过了return antedent.Result 位)!按原样完美工作(尝试添加ContinuationOptions.OnlyOnFaulted 等,但看不到任何可能的改进。)看起来我在循环通过ContinueWith 的23 个重载时睡着了:D 唯一缺少的位(如所指出的) @Drew Marsh 在前一种情况下)是显式 re-throw 将删除原始调用堆栈 - 即,理想情况下,我希望保留 throw; 语义(或 VB 过滤器:P)。假设没有人提出 no drop stacktrace impl,将在适当的时候接受。
    • @RubenBartelink 没问题。我使用TaskCompletionSource 添加了一个代码示例,它不会重新引发原始异常。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-04-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多