Подтвердить что ты не робот

Перехватить метод async, который возвращает общую задачу <> через DynamicProxy

Мои вопросы относятся к этому сообщению Перехват вызова метода async с использованием DynamicProxy

Я хочу реализовать перехватчик, который работает с асинхронными методами, который возвращает результат Task или Task<T>.

Я использую следующий код для результата return 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, которая не может быть передана напрямую.

4b9b3361

Ответ 1

После обширных исследований я смог создать решение, которое работает для перехвата синхронных методов, а также задачи Async и Async Task <TResult> .

Вот мой код для перехватчика Exception Handling, который работает на всех этих типах методов, используя 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
        }
    }

Ответ 2

Лучшим решением было бы использовать ключевое слово 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;
}

Ответ 3

Имея необходимость перехватывать методы, возвращающие Task<TResult>, я создал расширение для Castle.Core, которое упрощает процесс.

Castle.Core.AsyncInterceptor

Пакет доступен для загрузки на NuGet.

Решение в значительной степени основано на ответе @silas-reinagel, но упрощает его, предоставляя новый интерфейс для реализации IAsyncInterceptor. Есть также дополнительные абстракции, которые делают перехват похож на реализацию Interceptor.

Смотрите readme проекта для более подробной информации.

Ответ 4

Решения @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()
    {
    }
}

Ответ 5

Я сделал это так:

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);
}