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

C# 使用DynamicProxy拦截对异步方法的调用,c#,reflection,aop,async-await,castle-dynamicproxy,C#,Reflection,Aop,Async Await,Castle Dynamicproxy,下面是实现库的IInterceptor的自定义类型上的Intercept方法的代码。此代码段来自发布的基于日志的概念验证控制台应用程序 这在常规方法调用中可以正常工作,但在使用async方法(使用C#5.0中的async/await关键字)尝试时却无法正常工作。我相信,我也理解这背后的原因 为了使async/await工作,编译器在后台将方法的函数体添加到状态机中,一旦遇到第一个无法同步完成的awaitable表达式,控件就会返回给调用者 此外,我们还可以查询返回类型并确定是否正在处理一个asy

下面是实现库的
IInterceptor
的自定义类型上的
Intercept
方法的代码。此代码段来自发布的基于日志的概念验证控制台应用程序

这在常规方法调用中可以正常工作,但在使用
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...");
if(invocation.Method.ReturnType==typeof(Task)|
(invocation.Method.ReturnType.IsGenericType&&
invocation.Method.ReturnType.GetGenericTypeDefinition()==typeof(任务)))
Log.Info(“找到异步方法…”);
这只适用于那些返回
Task
Task
且不返回
void
async
方法,但我同意这一点

Intercept
方法中必须进行哪些更改,以使
等待者返回到那里,而不是原始调用者

大概“问题”在于,它只是在记录它返回的任务,而您想要该任务中的值

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

但是,在将任务返回给调用者之前,应该添加一个延续(via),它将在任务完成时记录结果(或失败)。这仍然会给出结果信息,但当然,在其他一些日志记录之后,您可能会对其进行日志记录。您可能还希望在返回之前立即进行日志记录,从而生成类似以下内容的日志:

            if (invocation.Method.ReturnType == typeof(Task) || 
                (invocation.Method.ReturnType.IsGenericType && 
                 invocation.Method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)))
                Log.Info("Asynchronous method found...");
称为fooancy
使用任务从FooAsync返回
来自FooAsync的任务已完成,返回值为5

从任务中获取结果(如果任务成功完成)的工作必须通过反射来完成,这有点麻烦——或者您可以使用动态键入。(无论哪种方式,它都会有一点性能上的冲击。)

多亏了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;
    }
}
公共无效截获(IInvocation调用)
{
if(Log.IsDebugEnabled)Log.Debug(CreateInvocationLogString(“调用”),invocation));
尝试
{
invocation.procedure();
if(Log.IsDebugEnabled)
{
var returnType=invocation.Method.returnType;
if(返回类型!=类型(无效))
{
var returnValue=invocation.returnValue;
if(returnType==typeof(任务))
{
Debug(“带任务返回”);
}
else if(returnType.IsGenericType&&returnType.GetGenericTypeDefinition()==typeof(任务))
{
Debug(“使用一般任务返回”);
var task=(task)returnValue;
task.ContinueWith((先行项)=>
{
var tasksdescriptor=CreateInvocationLogString(“任务来源”,调用);
var结果=
antecedent.GetType()
.GetProperty(“结果”)
.GetValue(先行项,空);
调试(taskDescriptor+“返回:”+结果);
});
}
其他的
{
Log.Debug(“返回值为:”+returnValue);
}
}
}
}
捕获(例外情况除外)
{
if(Log.IsErrorEnabled)Log.Error(CreateInvocationLogString(“Error”,invocation),ex);
投掷;
}
}
我的2美分:

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

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

因此,基于上述讨论和示例,这对于常规方法以及“原始”
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)
{
    ...
}
  • 但是当处理
    异步任务
    方法时,上述方法会错误地将拦截返回的任务类型从
    任务
    更改为常规
    任务

  • 请注意,我们调用的是
    Task.ContinueWith()
    ,而不是
    Task.ContinueWith()
    ,这是我们要调用的方法

  • 这将是最终等待此类拦截时产生的例外情况:

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


    下面是正确处理异步方法的异步侦听器适配器实现

    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 });
        }
    }
    
    抽象类异步侦听器:IInterceptor
    {
    类TaskCompletionSourceMethodMarkerAttribute:属性
    {
    }
    私有静态只读MethodInfo\u taskCompletionSourceMethod=typeof(异步侦听器)
    .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
    .单人(x=
    
    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;
        }
    }
    
       void IInterceptor.Intercept(IInvocation invocation) {
           try {
               invocation.Proceed();
               var task = invocation.ReturnValue as Task;
               if (task != null && task.IsFaulted) throw task.Exception;
           }
           catch {
               throw;
           }
       }
    
    tcs2.SetException(x.Exception);
    
    x.Exception.Handle(ex => { tcs2.SetException(ex); return true; });
    
    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;
    }