30

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

    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方法时(使用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...");

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

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

4

8 回答 8

20

大概“问题”是它只是记录它正在返回一个任务 - 你想要那个任务中的吗?

假设是这种情况,您仍然必须立即将任务返回给调用者 - 无需等待它完成。如果你打破了它,你从根本上把事情搞砸了。

但是,在将任务返回给调用者之前,您应该添加一个延续(通过Task.ContinueWith),它将在任务完成时记录结果(或失败) 。这仍然会提供结果信息,但当然您可能会在其他一些日志记录之后记录它。您可能还想在返回之前立即登录,从而导致类似这样的日志:

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

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

于 2013-01-11T23:14:29.887 回答
20

感谢乔恩的回答,这就是我最终得到的结果:

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;
    }
}
于 2013-01-12T00:22:36.887 回答
8

试图用一个通用和干净的解决方案来澄清:

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

我认为最好的解决方案是使用关键字绕过编译器类型检查并在运行时dynamic解决Task和Task之间的区别:<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;
}
于 2016-09-30T06:25:49.860 回答
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

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

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

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

于 2014-05-08T15:41:32.400 回答
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;
    }
}
于 2015-12-16T22:35:06.697 回答
2

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

Castle.Core.AsyncInterceptor

该软件包可在NuGet上下载。

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

有关详细信息,请参阅项目的自述文件

于 2017-10-27T11:02:36.593 回答
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;
       }
   }
于 2016-04-19T20:46:38.387 回答
-1

代替:

tcs2.SetException(x.Exception);

你应该使用:

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

冒泡真正的例外......

于 2016-06-24T01:27:59.447 回答