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

Har*_*til 27 c# reflection aop castle-dynamicproxy async-await

下面是从代码Intercept上实现自定义类型的方法IInterceptor的的城堡动态代理库.此片段来自此处发布的基于AOP的日志记录概念验证控制台应用程序.

    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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

为了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...");
Run Code Online (Sandbox Code Playgroud)

这适用于那些async返回TaskTask<>不返回的方法,void但我很好.

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

Jon*_*eet 19

据推测,"问题"在于它只记录它正在返回一项任务 - 你想要在该任务中获得价值吗?

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

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

Called FooAsync
Returned from FooAsync with a task
Task from FooAsync completed, with return value 5
Run Code Online (Sandbox Code Playgroud)

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


Har*_*til 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 有趣的事项需要注意:http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.asyncstatemachineattribute.aspx - 可以探测此属性以确定方法是否标记为异步,而不是依赖返回类型是`Task`或`Task <T>`的类型...... (6认同)
  • 感谢您分享已实施的解决方案,不仅要了解Jon Skeet给出的指导,还要了解您如何使用并应用它. (4认同)

the*_*000 7

尝试使用通用和清洁的解决方案来澄清:

  • 拦截async方法添加自定义代码作为延续任务.

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

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 logging here, as continuation work for Task...
}

private static async Task<T> InterceptAsync<T>(Task<T> task)
{
    T result = await task.ConfigureAwait(false);
    // do the logging here, as continuation work for Task<T>...
    return result;
}
Run Code Online (Sandbox Code Playgroud)