Castle DynamicProxy 动态代理-异步方法的代理(C#)
- Castle Core版本 v4.4.0 Github
- .net core 2.2
上一篇文章中我们介绍了Castle动态代理对于同步方法的动态代理,那么这篇文章就直接进入主题介绍如何使用Castle来对异步方法进行代理。
为何对异步方法会失效
首先为什么Castle会对异步方法(async-await)的动态代理失效?
还是考虑如下跟同步方法一样的代码:
public class SomeInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
//Do before...
invocation.Proceed();
//Do after...
}
}
当对异步方法进行代理的时候当调用到拦截器链底部,并且调用invocation.Proceed()
执行真正的被代理方法的时候由于被代理方法是异步的,所以当在异步方法里面碰到await
语句的时候,被代理方法会马上返回,然后执行invocation.Proceed()
后面的Do after。然而此时真正的被代理方法其实还没有执行完成。
所以就造成了Do after在被代理方法执行完成之前就已经执行了。
下面就介绍如何对这种异步方法进行动态代理。
返回值为Task
的方法
对于返回值为Task
的异步方法可以进行如下处理:
public class SomeInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
//Do before...
invocation.Proceed();
var returnType = invocation.Method.ReturnType; //获取被代理方法的返回类型
if(returnType != null && returnType == typeof(Task)) //判断如果为Task类型
{
Func<Task> continuation = async () => //定义一个异步方法来等待方法返回的Task
{
await (Task)invocation.ReturnValue;
//Do after... //方法返回后调用的代码
};
invocation.ReturnValue = continuation(); //设置返回值为刚刚定义的异步方法
return;
}
}
}
由于被代理方法是异步的,所以当调用invocation.Proceed()
时会马上返回,继续执行Intercept
下面的代码,而下面的代码是根据被代理方法的类型来判断是否是异步方法,然后通过构造一个匿名的函数来等待被代理方法返回,并且在下面继续编写返回后执行的代码。最后再通过设置invocation.ReturnValue = continuation()
来使方法阻塞在continuation()
实现了等待的效果。
注意
invocation.ReturnValue = continuation()
中并没有使用await,所以continuation()
就如同步方法一样是阻塞的。
返回值为Task<T>
的方法
为什么对于异步方法的代理需要按照返回值分开讨论?
最主要的原因在于Task<T>
的返回值是泛型类型,而Task
是固定的一种类型。
对与Task<T>
的异步方法代理的处理跟Task
类型的返回值的思路是一样的,唯一有一点不同的是需要利用反射来获取方法中的泛型类型,进而用来构造一个跟被代理方法返回值一样的临时方法
代码如下:
public class SomeInterceptor : IInterceptor
{
//利用反射获得等待返回值的异步方法,以便在后面构造泛型参数
private MethodInfo methodInfo = typeof(SomeInterceptor).GetMethod("HandleAsync", BindingFlags.Instance | BindingFlags.Public);
public void Intercept(IInvocation invocation)
{
//Do before...
invocation.Proceed();
var returnType = invocation.Method.ReflectedType; //获取被代理方法的返回类型
if(returnType != null && returnType.GetGenericTypeDefinition() == typeof(Task<>))
{
HandleAsyncWithReflection(invocation);
}
}
//利用反射获取方法类型信息来构造等待返回值的异步方法
private void HandleAsyncWithReflection(IInvocation invocation)
{
var resultType = invocation.Method.ReturnType.GetGenericArguments()[0];
var mi = methodInfo.MakeGenericMethod(resultType);
invocation.ReturnValue = mi.Invoke(this,new[] {invocation.ReturnValue});
}
//构造等待返回值的异步方法
public async Task<T> HandleAsync<T> (Task<T> task)
{
var t = await task;
//Do arter
return t;
}
}
上面的代码可能有少许的复杂,不过不要紧下面我就来解释一下为什么要这么做。
首先在类的开头多了一个methodInfo
字段,该字段是通过反射获得该类型中的方法名为HandleAsync
的方法信息。
然后我们再看正常的Intercept()
方法,这跟之前讨论的Task
返回值的一样,还是调用invocation.Proceed()
,然后还是判断返回值类型,不过这里是returnType.GetGenericTypeDefinition() == typeof(Task<>)
来判断是否为泛型的Task<>
对象。
然后如果是Task<>
类型的话就调用HandleAsyncWithReflection()
方法,那么这个方法是什么作用呢,看该方法的实现可以看到,该方法首先获取了一个泛型参数,即Task<T>
中实际的T
类型,然后调用methodInfo
字段的MakeGenericMethod()
方法,用获得的类型T
来重新构造HandleAsync()
方法。
然后还是跟之前一样,给invocation.ReturnValue
赋值,只是这时候是通过反射的方式来调用HandleAsync()
方法,进而等待方法执行完成并继续后面的动作。
总结
以上就是Castle动态代理中实现对异步方法的代理的解决方案,那么综合前面一篇文章Castle可以对所有的方法都提供动态代理,并且在github上也有关于用Castle来进行异步动态代理的库能减少一些代码量。
Castle.Core.AsyncInterceptor库的github地址 Castle.Core.AsyncInterceptor
本人的公众号,有空就写写这样子,谢谢关注