web-dev-qa-db-ja.com

DynamicProxyを介して一般的なTask <>を返す非同期メソッドをインターセプトします

私の質問はこの投稿に関連しています DynamicProxyを使用して非同期メソッドの呼び出しをインターセプトします

TaskまたはTask<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.ReturnValueTask<T>にキャストする必要があります。

動的にキャストする方法が見つかりませんでした。誰かがそれを作る方法を知っていますか?

また、リフレクションを介してこのメ​​ソッドを呼び出そうとしましたが、パラメーターは直接渡すことができないlabmda関数です。

28
Anatoliy

徹底的な調査の結果、同期メソッドと非同期タスクおよび非同期タスク<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
        }
    }
22
Silas Reinagel

より良い解決策は、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;
}
13
thepirat000

Task<TResult>を返すメソッドをインターセプトする必要があるため、プロセスを簡素化するCastle.Coreの拡張機能を作成しました。

Castle.Core.AsyncInterceptor

パッケージは NuGet でダウンロードできます。

このソリューションは、主に @ silas-reinagel からの回答に基づいていますが、実装するための新しいインターフェイスを提供することで簡素化しています IAsyncInterceptorInterceptorの実装と同様に、インターセプトを作成するためのさらなる抽象化もあります。

詳細については、プロジェクトの readme を参照してください。

4
James Skimming