2016-09-19 10 views
2

В основном у нас есть класс, который выглядит следующим образом, который использует Castle.DynamicProxy для перехвата.Можно ли создать асинхронный inteceptor с помощью Castle.DynamicProxy?

using System; 
using System.Collections.Concurrent; 
using System.Reflection; 
using System.Threading; 
using System.Threading.Tasks; 
using Castle.DynamicProxy; 

namespace SaaS.Core.IoC 
{ 
    public abstract class AsyncInterceptor : IInterceptor 
    { 
     private readonly ILog _logger; 

     private readonly ConcurrentDictionary<Type, Func<Task, IInvocation, Task>> wrapperCreators = 
      new ConcurrentDictionary<Type, Func<Task, IInvocation, Task>>(); 

     protected AsyncInterceptor(ILog logger) 
     { 
      _logger = logger; 
     } 

     void IInterceptor.Intercept(IInvocation invocation) 
     { 
      if (!typeof(Task).IsAssignableFrom(invocation.Method.ReturnType)) 
      { 
       InterceptSync(invocation); 
       return; 
      } 

      try 
      { 
       CheckCurrentSyncronizationContext(); 
       var method = invocation.Method; 

       if ((method != null) && typeof(Task).IsAssignableFrom(method.ReturnType)) 
       { 
        var taskWrapper = GetWrapperCreator(method.ReturnType); 
        Task.Factory.StartNew(
         async() => { await InterceptAsync(invocation, taskWrapper).ConfigureAwait(true); } 
         , // this will use current synchronization context 
         CancellationToken.None, 
         TaskCreationOptions.AttachedToParent, 
         TaskScheduler.FromCurrentSynchronizationContext()).Wait(); 
       } 
      } 
      catch (Exception ex) 
      { 
       //this is not really burring the exception 
       //excepiton is going back in the invocation.ReturnValue which 
       //is a Task that failed. with the same excpetion 
       //as ex. 
      } 
     } 
.... 

Первоначально этот код был:

Task.Run(async() => { await InterceptAsync(invocation, taskWrapper)).Wait() 

Но мы проигрывали HttpContext после любого вызова к этому, поэтому пришлось переключить его на:

Task.Factory.StartNew 

Таким образом, мы могли бы пройти в TaskScheduler.FromCurrentSynchronizationContext()

Все это плохо, потому что мы действительно просто с wapping один поток для другого потока. Я бы очень хотелось, чтобы изменить подпись

void IInterceptor.Intercept(IInvocation invocation) 

в

async Task IInterceptor.Intercept(IInvocation invocation) 

И избавиться от Task.Run или Task.Factory и просто сделать это:

await InterceptAsync(invocation, taskWrapper); 

Проблема это Castle.DynamicProxy IInterecptor не позволит этого. Я действительно хочу сделать ожидание в Intercept. Я мог бы сделать. Результат, но тогда в чем смысл асинхронного вызова, который я звоню? Не имея возможности сделать это, я проиграю из того, что он сможет выполнить это выполнение потоков. Я не придерживаюсь Castle Windsor для своего DynamicProxy, поэтому я ищу другой способ сделать это. Мы рассмотрели Unity, но я не хочу заменять всю реализацию AutoFac.

Любая помощь будет оценена по достоинству.

ответ

3

Все это плохо, потому что мы действительно просто заменяем один поток для другого потока.

Истина. Также потому, что версия StartNew на самом деле не ждет завершения этого метода; он будет ждать только до первого await. Но если вы добавите Unwrap(), чтобы дождаться полного метода, я сильно сомневаюсь, что вы закончите тупик.

Проблема в Castle.DynamicProxy IInterecptor не позволит этого.

IInterceptor имеет дизайн ограничения, что должен продолжить синхронно. Таким образом, это ограничивает возможности перехвата: вы можете вводить синхронный код до или после асинхронного метода и асинхронный код после асинхронного метода. Невозможно вводить асинхронный код перед асинхронным методом. Это просто ограничение DynamicProxy, которое было бы крайне болезненным для исправления (как, например, break всего существующего кода пользователя).

Чтобы сделать вид инъекции, поддерживает, вам нужно немного изменить свое мышление. Одной из допустимых умственных моделей async является то, что Task, возвращенный методом, представляет выполнение этого метода.Итак, чтобы добавить код к этому методу, вы вызываете метод напрямую, а затем заменяете возвращаемое значение задачи дополнительным.

Так, что-то вроде этого (для типов возвращаемых Task):

protected abstract void PreIntercept(); // must be sync 
protected abstract Task PostInterceptAsync(); // may be sync or async 

// This method will complete when PostInterceptAsync completes. 
private async Task InterceptAsync(Task originalTask) 
{ 
    // Asynchronously wait for the original task to complete 
    await originalTask; 

    // Asynchronous post-execution 
    await PostInterceptAsync(); 
} 

public void Intercept(IInvocation invocation) 
{ 
    // Run the pre-interception code. 
    PreIntercept(); 

    // *Start* the intercepted asynchronous method. 
    invocation.Proceed(); 

    // Replace the return value so that it only completes when the post-interception code is complete. 
    invocation.ReturnValue = InterceptAsync((Task)invocation.ReturnValue); 
} 

Обратите внимание, что PreIntercept, перехваченный метод, и PostInterceptAsync являются все бегут в исходном (ASP.NET) контексте.

P.S. Быстрый поиск Google для async DynamicProxy привел к this. Я даже не знаю, насколько это стабильно.

+1

Спасибо за ссылку, я проверю ее, и я приведу ваш пример. Я искал ответы на это в течение нескольких дней. –