通过DynamicProxy拦截返回通用Task <>的异步方法

28

我的问题与此帖子相关:使用DynamicProxy拦截异步方法的调用

我想实现一个拦截器,它可以处理返回TaskTask<T>结果的异步方法。

我使用以下代码返回ContinueWith结果(以便调用者方法等待拦截器完成工作)

var task = invocation.ReturnValue as Task;
invocation.ReturnValue = task.ContinueWith(c => 
      { code that should execute after method finish });

以上代码对于Task的结果运行良好,但在Task<T>结果的情况下,ContinueWith将把返回类型从Task<T>更改为Task。 我需要调用重载的方法ContinueWith,它返回Task<T>,但是为此我需要将invocation.ReturnValue强制转换为Task<T>

我没有找到以任何方式动态强制转换的方法。 有人知道如何做吗?

我还尝试通过反射调用此方法,但参数是无法直接传递的lambda函数。


1
请查看https://msdn.microsoft.com/en-us/magazine/dn574805.aspx - 该文章是关于Unity拦截器的,但“wrap the task”代码不依赖于您的DI容器。 - Alexei Levenkov
5个回答

24

通过广泛的研究,我成功地创建了一种解决方案,可以拦截同步方法以及异步任务和异步任务<TResult>。

这是我的代码,用于所有这些方法类型的异常处理拦截器,使用Castle Dynamic Proxy。该模式适用于执行任何您希望进行的拦截。对于标准的BeforeInvoke/AfterInvoke操作,语法会更加简洁,但概念应该是相同的。

(其他说明:示例中的IExceptionHandler接口是自定义类型,而不是常见对象。)

    private class AsyncExceptionHandlingInterceptor : IInterceptor
    {
        private static readonly MethodInfo handleAsyncMethodInfo = typeof(AsyncExceptionHandlingInterceptor).GetMethod("HandleAsyncWithResult", BindingFlags.Instance | BindingFlags.NonPublic);
        private readonly IExceptionHandler _handler;

        public AsyncExceptionHandlingInterceptor(IExceptionHandler handler)
        {
            _handler = handler;
        }

        public void Intercept(IInvocation invocation)
        {
            var delegateType = GetDelegateType(invocation);
            if (delegateType == MethodType.Synchronous)
            {
                _handler.HandleExceptions(() => invocation.Proceed());
            }
            if (delegateType == MethodType.AsyncAction)
            {
                invocation.Proceed();
                invocation.ReturnValue = HandleAsync((Task)invocation.ReturnValue);
            }
            if (delegateType == MethodType.AsyncFunction)
            {
                invocation.Proceed();
                ExecuteHandleAsyncWithResultUsingReflection(invocation);
            }
        }

        private void ExecuteHandleAsyncWithResultUsingReflection(IInvocation invocation)
        {
            var resultType = invocation.Method.ReturnType.GetGenericArguments()[0];
            var mi = handleAsyncMethodInfo.MakeGenericMethod(resultType);
            invocation.ReturnValue = mi.Invoke(this, new[] { invocation.ReturnValue });
        }

        private async Task HandleAsync(Task task)
        {
            await _handler.HandleExceptions(async () => await task);
        }

        private async Task<T> HandleAsyncWithResult<T>(Task<T> task)
        {
            return await _handler.HandleExceptions(async () => await task);
        }

        private MethodType GetDelegateType(IInvocation invocation)
        {
            var returnType = invocation.Method.ReturnType;
            if (returnType == typeof(Task))
                return MethodType.AsyncAction;
            if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
                return MethodType.AsyncFunction;
            return MethodType.Synchronous;
        }

        private enum MethodType
        {
            Synchronous,
            AsyncAction,
            AsyncFunction
        }
    }

1
感谢您提供这个,这正是我正在寻找的。为了让其他人清楚,这里是IExceptionHandler接口方法。在我的实现中,我实际上将这些类重命名为通用类,以便在拦截任何可能拦截异步方法的拦截器时使用。void Handle(Action synchronousInvoker)Task Handle(Func<Task> awaitableInvoker)Task<T> Handle<T>(Func<Task<T>> awaitableInvoker) - Matt Scully
我看到 'HandleAsyncWithResult' 方法从未被调用。您在示例代码中忘记了什么吗?我看到在 'AsyncFunction' 异常的情况下不会被处理。 - Krzysztof Branicki
1
“HandleAsyncWithResult” 方法是从 “ExecuteHandleAsyncWithResultUsingReflection” 方法中迂回调用的。异常由 .NET 任务对象处理,并在调用时正确抛出。 - Silas Reinagel

16
一种更好的解决方案是使用 dynamic 关键字绕过编译器类型检查,在运行时解析该操作:
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 continuation work for Task...
}

private static async Task<T> InterceptAsync<T>(Task<T> task)
{
    T result = await task.ConfigureAwait(false);
    // do the continuation work for Task<T>...
    return result;
}

1
这是一个更为简洁的解决方案! - iamyojimbo
你认为为什么这个条件 typeof(Task).IsAssignableFrom(method.ReturnType) 不足以处理异步拦截呢? - King King
1
这是一个很棒的想法。我借助它创建了一个帮助包,感谢它。需要的人请访问:https://github.com/wswind/lightwind/blob/master/src/Lightwind.AsyncInterceptor/AsyncInterceptorBase.cs。 - ws_

7

我需要拦截返回 Task<TResult> 的方法,所以我创建了一个扩展程序来简化这个过程,该扩展程序是基于 Castle.Core 的。

Castle.Core.AsyncInterceptor

该软件包可在NuGet上下载。

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

有关更多详细信息,请参见项目的readme


0

@Silas Reinagel和@thepirat000的解决方案对我没有用,而且我也没有成功使用@James Skimming的Castle.Core.AsyncInterceptor解决方案。

在我的情况下,我正在拦截返回Task的异步方法,并且只有在invocation.Proceed()期间没有异常时才应执行“after invocation.Proceed() code”。最后,我使用了@James Skimming的代码示例,因此此解决方案仅适用于拦截返回Task而不是Task<TResult>的异步方法:

public void Intercept(IInvocation invocation)
{
    _stepPriorInvocation();

    invocation.Proceed();
    Func<Task> continuation = async () =>
    {
        await (Task)invocation.ReturnValue;

        _stepAfterSuccessfulInvocation();
    };

    invocation.ReturnValue = continuation();

    void _stepPriorInvocation()
    {
    }

    void _stepAfterSuccessfulInvocation()
    {
    }
}


-1
我是这样做的:
invocation.Proceed(); 
object response;
Type type = invocation.ReturnValue?.GetType();
if (type != null && typeof(Task).IsAssignableFrom(type))
{
    var resultProperty = type.GetProperty("Result");
    response = resultProperty.GetValue(invocation.ReturnValue);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接