Перехватить асинхронный метод, который возвращает универсальную задачу <> через DynamicProxy
Мои вопросы связаны с этим постом Перехватить вызов асинхронного метода с помощью 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, которую нельзя передать напрямую.
4 ответа
После обширных исследований я смог создать решение, которое работает для перехвата синхронных методов, а также задач Async и Async
Вот мой код для перехватчика обработки исключений, который работает со всеми этими типами методов, используя 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
это упрощает процесс.
https://github.com/JSkimming/Castle.Core.AsyncInterceptor
Пакет доступен для скачивания на NuGet.
Решение во многом основано на ответе Silas Reinagel, но упрощает его, предоставляя новый интерфейс для реализации IAsyncInterceptor. Есть также дополнительные абстракции, которые делают перехват похож на реализацию Interceptor
,
Смотрите readme проекта для более подробной информации.
Решения от @Silas Reinagel и @thepirat000 у меня не работали, и я не смог использовать Castle.Core.AsyncInterceptor
решение от @James Skimming.
В моем случае я перехватываю асинхронный метод, возвращающий Task
, и он должен выполняться "после кода invocation.Proceed()" только в том случае, если во время invocation.Proceed()
. В конце я использовал образец кода@James Skimming, поэтому это решение работает только для перехвата асинхронных методов, возвращающихTask
и нет Task<TResult>
:
public void Intercept(IInvocation invocation)
{
_stepPriorInvocation();
invocation.Proceed();
Func<Task> continuation = async () =>
{
await (Task)invocation.ReturnValue;
_stepAfterSuccessfulInvocation();
};
invocation.ReturnValue = continuation();
void _stepPriorInvocation()
{
}
void _stepAfterSuccessfulInvocation()
{
}
}
Я сделал так:
invocation.Proceed();
object response;
Type type = invocation.ReturnValue?.GetType();
if (type != null && typeof(Task).IsAssignableFrom(type))
{
var resultProperty = type.GetProperty("Result");
response = resultProperty.GetValue(invocation.ReturnValue);
}