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.ReflectedType; //獲取被代理方法的返回類型
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,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
本人的公衆號,有空就寫寫這樣子,謝謝關注