使用DynamicProxy拦截对异步方法的调用

时间:2013-01-11 23:03:27

标签: c# reflection aop async-await castle-dynamicproxy

以下是实现Castle Dynamic Proxy库的Intercept的自定义类型的IInterceptor方法的代码。此代码段来自基于AOP的日志记录概念验证控制台应用程序,该应用程序已发布here

    public void Intercept(IInvocation invocation)
    {
        if (Log.IsDebugEnabled) Log.Debug(CreateInvocationLogString("Called", invocation));
        try
        {
            invocation.Proceed();
            if (Log.IsDebugEnabled)
                if (invocation.Method.ReturnType != typeof(void))
                    Log.Debug("Returning with: " + invocation.ReturnValue);
        }
        catch (Exception ex)
        {
            if (Log.IsErrorEnabled) Log.Error(CreateInvocationLogString("ERROR", invocation), ex);
            throw;
        }
    }

这在常规方法调用中按预期工作,但在尝试使用async方法时(使用C#5.0中的async/await关键字)则不行。我相信,我也理解这背后的原因。

为了async/await工作,编译器将方法的功能主体添加到幕后的状态机中,一旦第一个awaitable表达式,控件将返回到调用者遇到不能同步完成。

此外,我们可以询问返回类型并确定我们是否正在处理这样的async方法:

            if (invocation.Method.ReturnType == typeof(Task) || 
                (invocation.Method.ReturnType.IsGenericType && 
                 invocation.Method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)))
                Log.Info("Asynchronous method found...");

这仅适用于那些返回asyncTask而不是Task<>的{​​{1}}方法,但我很满意。

void方法中需要进行哪些更改才能使Intercept返回到原始调用方而不是原始调用方?

8 个答案:

答案 0 :(得分:19)

大概是&#34;问题&#34;是因为它只记录它返回任务 - 你想要在该任务中使用吗?

假设情况如此,您仍然必须立即将任务返回给调用者 - 而不必等待它完成。如果你打破了这一点,你就会从根本上弄乱事情。

但是,在将任务返回给调用者之前,您应该添加一个继续(通过Task.ContinueWith),它将在任务完成时记录结果(或失败) 。这仍将提供结果信息,但当然,您可能会在其他日志记录之后将其记录下来。您可能想要在返回之前立即记录,从而产生如下所示的日志:

Called FooAsync
Returned from FooAsync with a task
Task from FooAsync completed, with return value 5

将结果从任务中取出(如果成功完成)的业务必须通过反射完成,这有点痛苦 - 或者您可以使用动态类型。 (无论哪种方式,它都会受到性能影响。)

答案 1 :(得分:17)

感谢Jon的回答,这就是我最终的结果:

public void Intercept(IInvocation invocation)
{
    if (Log.IsDebugEnabled) Log.Debug(CreateInvocationLogString("Called", invocation));
    try
    {
        invocation.Proceed();

        if (Log.IsDebugEnabled)
        {
            var returnType = invocation.Method.ReturnType;
            if (returnType != typeof(void))
            {
                var returnValue = invocation.ReturnValue;
                if (returnType == typeof(Task))
                {
                    Log.Debug("Returning with a task.");
                }
                else if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
                {
                    Log.Debug("Returning with a generic task.");
                    var task = (Task)returnValue;
                    task.ContinueWith((antecedent) =>
                                          {
                                              var taskDescriptor = CreateInvocationLogString("Task from", invocation);
                                              var result =
                                                  antecedent.GetType()
                                                            .GetProperty("Result")
                                                            .GetValue(antecedent, null);
                                              Log.Debug(taskDescriptor + " returning with: " + result);
                                          });
                }
                else
                {
                    Log.Debug("Returning with: " + returnValue);
                }
            }
        }
    }
    catch (Exception ex)
    {
        if (Log.IsErrorEnabled) Log.Error(CreateInvocationLogString("ERROR", invocation), ex);
        throw;
    }
}

答案 2 :(得分:6)

尝试使用通用且干净的解决方案进行澄清:

  • 拦截$('body').on('remove', '.selector', function() { console.log('Element removed.'); }); 方法,将自定义代码添加为延续任务。

我认为最好的解决方案是使用async关键字绕过编译器类型检查并在运行时解决任务和任务dynamic之间的区别:

<T>

答案 3 :(得分:4)

我的2美分:

已经正确地确定,对于async方法,拦截器的目的是通过延续来“增强”通过调用返回的任务

现在,正是这个任务延续必须返回才能完成拦截器的工作。

因此,基于上述讨论和示例,这对于常规方法以及“原始”async Task方法非常有效。

public virtual void Intercept(IInvocation invocation)
{
    try
    {
        invocation.Proceed();
        var task = invocation.ReturnValue as Task;
        if (task != null)
        {
            invocation.ReturnValue = task.ContinueWith(t => {
                if (t.IsFaulted)
                    OnException(invocation, t.Exception);
            });
        }
    }
    catch (Exception ex)
    {
        OnException(invocation, ex);
    }
}

public virtual void OnException(IInvocation invocation, Exception exception)
{
    ...
}
  1. 但在处理async Task<T>方法时,上述方法会错误地更改拦截返回的任务类型,从Task<T>到常规Task

    < / LI>
  2. 请注意,我们正在调用Task.ContinueWith()而不是Task<TResult>.ContinueWith(),这是我们要调用的方法。

  3. 这将是最终等待此类拦截时产生的异常:

      

    System.InvalidCastException:无法将类型为“System.Threading.Tasks.ContinuationTaskFromTask”的对象强制转换为'System.Threading.Tasks.Task`1

答案 4 :(得分:4)

下面是我正确处理异步方法的异步拦截器适配器实现。

abstract class AsyncInterceptor : IInterceptor
{
    class TaskCompletionSourceMethodMarkerAttribute : Attribute
    {

    }

    private static readonly MethodInfo _taskCompletionSourceMethod = typeof(AsyncInterceptor)
        .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
        .Single(x => x.GetCustomAttributes(typeof(TaskCompletionSourceMethodMarkerAttribute)).Any());


    protected virtual Task<Object> InterceptAsync(Object target, MethodBase method, object[] arguments, Func<Task<Object>> proceed)
    {
        return proceed();
    }

    protected virtual void Intercept(Object target, MethodBase method, object[] arguments, Action proceed)
    {
        proceed();
    }

    [TaskCompletionSourceMethodMarker]
    Task<TResult> TaskCompletionSource<TResult>(IInvocation invocation)
    {
        var tcs = new TaskCompletionSource<TResult>();

        var task = InterceptAsync(invocation.InvocationTarget, invocation.Method, invocation.Arguments, () =>
        {
            var task2 = (Task)invocation.Method.Invoke(invocation.InvocationTarget, invocation.Arguments);
            var tcs2 = new TaskCompletionSource<Object>();
            task2.ContinueWith(x =>
            {
                if (x.IsFaulted)
                {
                    tcs2.SetException(x.Exception);
                    return;
                }
                dynamic dynamicTask = task2;
                Object result = dynamicTask.Result;
                tcs2.SetResult(result);
            });
            return tcs2.Task;
        });

        task.ContinueWith(x =>
        {
            if (x.IsFaulted)
            {
                tcs.SetException(x.Exception);
                return;
            }

            tcs.SetResult((TResult)x.Result);
        });

        return tcs.Task;
    }
    void IInterceptor.Intercept(IInvocation invocation)
    {
        if (!typeof(Task).IsAssignableFrom(invocation.Method.ReturnType))
        {
            Intercept(invocation.InvocationTarget, invocation.Method, invocation.Arguments, invocation.Proceed);
            return;
        }
        var returnType = invocation.Method.ReturnType.IsGenericType ? invocation.Method.ReturnType.GetGenericArguments()[0] : typeof(object);
        var method = _taskCompletionSourceMethod.MakeGenericMethod(returnType);
        invocation.ReturnValue = method.Invoke(this, new object[] { invocation });
    }
}

和样本用法:

class TestInterceptor : AsyncInterceptor
{
    protected override async Task<Object> InterceptAsync(object target, MethodBase method, object[] arguments, Func<Task<object>> proceed)
    {
        await Task.Delay(5000);
        var result = await proceed();
        return DateTime.Now.Ticks % 2 == 0 ? 10000 :result;
    }
}

答案 5 :(得分:1)

需要拦截返回Task<TResult>的方法,我创建了Castle.Core的扩展,简化了流程。

Castle.Core.AsyncInterceptor

该套餐可在NuGet上下载。

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

有关详细信息,请参阅项目的readme

答案 6 :(得分:0)

   void IInterceptor.Intercept(IInvocation invocation) {
       try {
           invocation.Proceed();
           var task = invocation.ReturnValue as Task;
           if (task != null && task.IsFaulted) throw task.Exception;
       }
       catch {
           throw;
       }
   }

答案 7 :(得分:-1)

而不是:

tcs2.SetException(x.Exception);

您应该使用:

x.Exception.Handle(ex => { tcs2.SetException(ex); return true; });

冒出真正的例外......