私の質問はこの投稿に関連しています 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.ReturnValue
をTask<T>
にキャストする必要があります。
動的にキャストする方法が見つかりませんでした。誰かがそれを作る方法を知っていますか?
また、リフレクションを介してこのメソッドを呼び出そうとしましたが、パラメーターは直接渡すことができないlabmda関数です。
徹底的な調査の結果、同期メソッドと非同期タスクおよび非同期タスク<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
}
}
より良い解決策は、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;
}
Task<TResult>
を返すメソッドをインターセプトする必要があるため、プロセスを簡素化するCastle.Core
の拡張機能を作成しました。
パッケージは NuGet でダウンロードできます。
このソリューションは、主に @ silas-reinagel からの回答に基づいていますが、実装するための新しいインターフェイスを提供することで簡素化しています IAsyncInterceptor 。 Interceptor
の実装と同様に、インターセプトを作成するためのさらなる抽象化もあります。
詳細については、プロジェクトの readme を参照してください。