天天看點

Castle 動态代理-異步方法的代理Castle DynamicProxy 動态代理-異步方法的代理(C#)

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

本人的公衆号,有空就寫寫這樣子,謝謝關注

Castle 動态代理-異步方法的代理Castle DynamicProxy 動态代理-異步方法的代理(C#)