【问题标题】:Create an exception-converting dynamic proxy that works with tasks创建一个处理任务的异常转换动态代理
【发布时间】:2015-06-15 14:17:30
【问题描述】:

问题

我不知何故绕着圈子跑……我尝试使用 Castle Dynamic Proxy 创建一个interface proxy with target。代理应该

  • 如果没有抛出异常(即什么都不做),则返回调用的返回值。
  • 如果调用抛出 InvalidOperationException,则抛出新的 InterceptedException
  • 如果调用引发另一个异常e,则引发e

换句话说,拦截器应该捕获并转换特定的异常类型,而不是在所有其他情况下拦截。

我把它用于同步方法。但是,对于返回任务的异步方法,我需要相同的行为。

我尝试了什么

我尝试向返回的任务添加延续并检查IsFaultedException(类似于this answer。这适用于返回Task 的方法,但不适用于自我延续以来返回Task<T> 的方法是Task 类型(我不知道拦截器中的T 是什么)。

涵盖上述三种异步方法情况的测试 (XUnit.net)

public class ConvertNotFoundInterceptorTest
{
    [Fact]
    public void Non_throwing_func_returns_a_result()
    {
        Assert.Equal(43, RunTest(i => i + 1));
    }

    [Fact]
    public void InvalidOperationExceptions_are_converted_to_IndexOutOfRangeExceptions()
    {
        var exception = Assert.Throws<AggregateException>(() => RunTest(i => { throw new InvalidOperationException("ugh"); }));
        Assert.True(exception.InnerException is IndexOutOfRangeException);
    }

    [Fact]
    public void Other_exceptions_are_preserved()
    {
        var exception = Assert.Throws<AggregateException>(() => RunTest(i => { throw new ArgumentException("ugh"); }));
        Assert.True(exception.InnerException is ArgumentException);
    }

    private static int RunTest(Func<int, int> func)
    {
        var generator = new ProxyGenerator();

        var proxiedSubject = generator.CreateInterfaceProxyWithTarget<ISubject>(new Subject(func), new ConvertNotFoundInterceptor());

        return proxiedSubject.DoAsync(42).Result;
    }

    public interface ISubject
    {
        Task<int> DoAsync(int input);
    }

    public class Subject : ISubject
    {
        private readonly Func<int, int> _func;

        public Subject(Func<int, int> func)
        {
            _func = func;
        }

        public async Task<int> DoAsync(int input)
        {
            return await Task.Run(() => _func(input));
        }
    }
}

拦截器

public class ConvertNotFoundInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        invocation.Proceed();

        var task = invocation.ReturnValue as Task;
        if (task != null)
        {
            var continuation = task.ContinueWith(
                t =>
                {
                    if (t.Exception != null && t.Exception.InnerException is InvalidOperationException)
                    {
                        throw new IndexOutOfRangeException();
                    }
                }, TaskContinuationOptions.OnlyOnFaulted);

            // The following line fails (InvalidCastException: Unable to cast object 
            // of type 'System.Threading.Tasks.ContinuationTaskFromTask' 
            // to type 'System.Threading.Tasks.Task`1[System.Int32]'.)
            invocation.ReturnValue = continuation;
        }
    }
}

请注意,此处显示的实现不考虑同步情况。我故意漏掉了那部分。

问题

将上述拦截逻辑添加到异步方法中的正确方法是什么?

【问题讨论】:

  • 我相信您应该能够通过返回 TaskTask&lt;dynamic&gt; 来完成此工作,具体取决于拦截的方法返回的内容。 try/catch 块围绕 await 应该可以正常工作。你能发布你现有的代码吗?
  • @StephenCleary 我用现有代码更新了我的答案。返回任务(如示例中)或任务 都不起作用,都产生 InvalidCastException。

标签: c# task castle-dynamicproxy


【解决方案1】:

好的,这不适用于 Task&lt;dynamic&gt;,因为 Castle Dynamic Proxy 要求 ReturnValue 是完全匹配的类型。但是,您可以通过使用dynamic 进行调度来相当优雅地完成此操作:

public class ConvertNotFoundInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        invocation.Proceed();

        var task = invocation.ReturnValue as Task;
        if (task != null)
            invocation.ReturnValue = ConvertNotFoundAsync((dynamic)task);
    }

    private static async Task ConvertNotFoundAsync(Task source)
    {
        try
        {
            await source.ConfigureAwait(false);
        }
        catch (InvalidOperationException)
        {
            throw new IndexOutOfRangeException();
        }
    }

    private static async Task<T> ConvertNotFoundAsync<T>(Task<T> source)
    {
        try
        {
            return await source.ConfigureAwait(false);
        }
        catch (InvalidOperationException)
        {
            throw new IndexOutOfRangeException();
        }
    }
}

我非常喜欢 async/await 语法,因为它们可以正确处理使用 ContinueWith 时难以处理的边缘情况。

【讨论】:

  • 很好,这行得通!你能告诉我source.ConfigureAwait(false) 在这种情况下做了什么吗?恐怕文档对我没有什么帮助。
  • By default, await captures a context.(我在我的博客上详细描述了这一点)。 ConfigureAwait(false) 避免了上下文捕获,因为这里没有必要。
猜你喜欢
  • 1970-01-01
  • 2014-03-06
  • 1970-01-01
  • 1970-01-01
  • 2015-07-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多