拦截该呼叫使用DynamicProxy异步方法(Intercept the call to an a

2019-07-18 03:01发布

下面是从代码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;
        }
    }

这是工作预期在普通方法调用,但是当试图不async方法(使用async/await从C#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会回到那里,而不是原来的调用者?

Answer 1:

据推测“问题”是,它只是记录,它的返回一个任务-你想要的任务中的价值

假如是这样的话,你还是要任务返回给调用者,立即 - 无需等待它完成。 如果你打破,你就从根本上搞乱的东西了。

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

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

得到的结果是淘汰任务(如果它成功完成)的业务将不得不与反思完成,这是一个有点疼痛的 - 或者你可以使用动态类型。 (无论哪种方式,这将是一个有点性能损失。)



Answer 2:

由于乔恩的答案,这是我结束了:

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;
    }
}


Answer 3:

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

  • 拦截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;
}


Answer 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;
    }
}


Answer 5:

我的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`1



Answer 6:

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

Castle.Core.AsyncInterceptor

该软件包可供下载上的NuGet 。

该解决方案主要是基于此答案由@西拉斯- reinagel ,而是通过提供新的接口来实现简化它IAsyncInterceptor 。 也有进一步的抽象,以使拦截类似于实现Interceptor

请参阅自述项目作进一步的细节。



Answer 7:

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


Answer 8:

代替:

tcs2.SetException(x.Exception);

您应该使用:

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

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



文章来源: Intercept the call to an async method using DynamicProxy