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
對于傳回值為
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()
實作了等待的效果。
注意中并沒有使用await,是以
invocation.ReturnValue = continuation()
就如同步方法一樣是阻塞的。
continuation()
傳回值為 Task<T>
的方法
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
本人的公衆号,有空就寫寫這樣子,謝謝關注