【问题标题】:Intercept async method that returns generic Task<> via DynamicProxy拦截通过 DynamicProxy 返回通用 Task<> 的异步方法
【发布时间】:2019-09-19 20:25:06
【问题描述】:

我的问题与这篇帖子有关Intercept the call to an async method using DynamicProxy

我想实现与返回 TaskTask&lt;T&gt; 结果的异步方法一起使用的拦截器。

我使用下一个代码返回 ContinueWith 结果(以便调用者方法在拦截器完成工作时等待)

var task = invocation.ReturnValue as Task;
invocation.ReturnValue = task.ContinueWith(c => 
      { code that should execute after method finish });

以上代码适用于Task 结果,但如果Task&lt;T&gt; 结果ContinueWith 将返回类型从Task&lt;T&gt; 更改为Task。 我需要调用返回Task&lt;T&gt; 的重载方法ContinueWith,但为此我需要将invocation.ReturnValue 转换为Task&lt;T&gt;

我没有找到以任何方式动态投射它的方法。 有人知道怎么做吗?

我也试过通过反射调用这个方法,但是参数是labmda函数,不能直接传递。

【问题讨论】:

标签: c# generics async-await castle-dynamicproxy


【解决方案1】:

经过广泛研究,我能够创建一个适用于拦截同步方法以及异步任务和异步任务的解决方案。

这是我的异常处理拦截器代码,它使用 Castle Dynamic Proxy 处理所有这些方法类型。这种模式适用于做任何你想做的拦截。对于标准的 BeforeInvoke/AfterInvoke 操作,语法会更简洁一些,但概念应该相同。

(其他说明:示例中的IExceptionHandler接口是自定义类型,不是普通对象。)

    private class AsyncExceptionHandlingInterceptor : IInterceptor
    {
        private static readonly MethodInfo handleAsyncMethodInfo = typeof(AsyncExceptionHandlingInterceptor).GetMethod("HandleAsyncWithResult", BindingFlags.Instance | BindingFlags.NonPublic);
        private readonly IExceptionHandler _handler;

        public AsyncExceptionHandlingInterceptor(IExceptionHandler handler)
        {
            _handler = handler;
        }

        public void Intercept(IInvocation invocation)
        {
            var delegateType = GetDelegateType(invocation);
            if (delegateType == MethodType.Synchronous)
            {
                _handler.HandleExceptions(() => invocation.Proceed());
            }
            if (delegateType == MethodType.AsyncAction)
            {
                invocation.Proceed();
                invocation.ReturnValue = HandleAsync((Task)invocation.ReturnValue);
            }
            if (delegateType == MethodType.AsyncFunction)
            {
                invocation.Proceed();
                ExecuteHandleAsyncWithResultUsingReflection(invocation);
            }
        }

        private void ExecuteHandleAsyncWithResultUsingReflection(IInvocation invocation)
        {
            var resultType = invocation.Method.ReturnType.GetGenericArguments()[0];
            var mi = handleAsyncMethodInfo.MakeGenericMethod(resultType);
            invocation.ReturnValue = mi.Invoke(this, new[] { invocation.ReturnValue });
        }

        private async Task HandleAsync(Task task)
        {
            await _handler.HandleExceptions(async () => await task);
        }

        private async Task<T> HandleAsyncWithResult<T>(Task<T> task)
        {
            return await _handler.HandleExceptions(async () => await task);
        }

        private MethodType GetDelegateType(IInvocation invocation)
        {
            var returnType = invocation.Method.ReturnType;
            if (returnType == typeof(Task))
                return MethodType.AsyncAction;
            if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
                return MethodType.AsyncFunction;
            return MethodType.Synchronous;
        }

        private enum MethodType
        {
            Synchronous,
            AsyncAction,
            AsyncFunction
        }
    }

【讨论】:

  • 感谢您提供这个,这正是我想要的。对其他人来说很清楚,这是 IExceptionHandler 接口方法。在我的实现中,我实际上将这些类重命名为通用的,以便在任何可能拦截异步方法的拦截器中使用。 void Handle(Action synchronousInvoker), Task Handle(Func&lt;Task&gt; awaitableInvoker), Task&lt;T&gt; Handle&lt;T&gt;(Func&lt;Task&lt;T&gt;&gt; awaitableInvoker).
  • 我看到从未调用过“HandleAsyncWithResult”方法。您是否忘记了示例代码中的某些内容?我看到如果出现“AsyncFunction”异常,将不会被处理。
  • 从 'ExecuteHandleAsyncWithResultUsingReflection' 方法以迂回方式调用 'HandleAsyncWithResult' 方法。异常由 .NET Task 对象处理,并在调用时正确抛出。
【解决方案2】:

更好的解决方案是使用dynamic 关键字绕过编译器类型检查并在运行时解析操作:

public void Intercept(IInvocation invocation)
{
    invocation.Proceed();
    var method = invocation.MethodInvocationTarget;
    var isAsync = method.GetCustomAttribute(typeof(AsyncStateMachineAttribute)) != null;
    if (isAsync && typeof(Task).IsAssignableFrom(method.ReturnType))
    {
        invocation.ReturnValue = InterceptAsync((dynamic)invocation.ReturnValue);
    }
}

private static async Task InterceptAsync(Task task)
{
    await task.ConfigureAwait(false);
    // do the continuation work for Task...
}

private static async Task<T> InterceptAsync<T>(Task<T> task)
{
    T result = await task.ConfigureAwait(false);
    // do the continuation work for Task<T>...
    return result;
}

【讨论】:

  • 这是一个更简洁的解决方案!
  • 为什么你认为typeof(Task).IsAssignableFrom(method.ReturnType)这个条件不足以继续处理异步拦截?
  • 这是个好主意。我在它的帮助下创建了一个帮助包,谢谢。有需要的请访问:github.com/wswind/lightwind/blob/master/src/…>
【解决方案3】:

由于需要拦截返回 Task&lt;TResult&gt; 的方法,我创建了 Castle.Core 的扩展来简化流程。

Castle.Core.AsyncInterceptor

该软件包可在NuGet 下载。

该解决方案主要基于@silas-reinagel 的答案,但通过提供一个新接口来实现IAsyncInterceptor 来简化它。还有一些进一步的抽象,使拦截类似于实现Interceptor

更多详情请查看项目的readme

【讨论】:

    【解决方案4】:

    @Silas Reinagel 和@thepirat000 的解决方案对我不起作用,我使用@James Skimming 的Castle.Core.AsyncInterceptor 解决方案也没有成功。

    在我的例子中,我拦截了一个返回 Task 的异步方法,并且只有在 invocation.Proceed() 期间没有异常时它才应该执行“在 invocation.Proceed() 代码之后”。最后我使用了@James Skimming 的code sample,所以这个解决方案只适用于拦截返回Task 而不是Task&lt;TResult&gt; 的异步方法:

    public void Intercept(IInvocation invocation)
    {
        _stepPriorInvocation();
    
        invocation.Proceed();
        Func<Task> continuation = async () =>
        {
            await (Task)invocation.ReturnValue;
    
            _stepAfterSuccessfulInvocation();
        };
    
        invocation.ReturnValue = continuation();
    
        void _stepPriorInvocation()
        {
        }
    
        void _stepAfterSuccessfulInvocation()
        {
        }
    }
    
    

    【讨论】:

      【解决方案5】:

      我是这样做的:

      invocation.Proceed(); 
      object response;
      Type type = invocation.ReturnValue?.GetType();
      if (type != null && typeof(Task).IsAssignableFrom(type))
      {
          var resultProperty = type.GetProperty("Result");
          response = resultProperty.GetValue(invocation.ReturnValue);
      }
      

      【讨论】:

        猜你喜欢
        • 2012-12-26
        • 2018-09-08
        • 1970-01-01
        • 2013-09-04
        • 2018-10-06
        • 1970-01-01
        • 2021-05-11
        • 2016-10-28
        • 1970-01-01
        相关资源
        最近更新 更多