2016-09-19 6 views
2

Nous avons essentiellement une classe qui ressemble à ceci ci-dessous qui utilise le Castle.DynamicProxy pour l'interception.Est-il possible de créer un intépteur asynchrone en utilisant 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. 
      } 
     } 
.... 

Initialement ce code était:

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

Mais nous perdions HttpContext après tout appel à cela, nous avons donc dû passer à:

Task.Factory.StartNew 

On pourrait donc passer the TaskScheduler.FromCurrentSynchronizationContext()

Tout cela est mauvais parce que nous sommes vraiment juste s wapping un fil pour un autre fil. J'aimerais vraiment changer la signature de

void IInterceptor.Intercept(IInvocation invocation) 

à

async Task IInterceptor.Intercept(IInvocation invocation) 

Et se débarrasser de la Task.Run ou Task.Factory et juste faire:

await InterceptAsync(invocation, taskWrapper); 

Le problème Castle.DynamicProxy IInterecptor ne le permet pas. Je veux vraiment faire une attente dans l'Intercept. Je pourrais faire .Result mais alors quel est le point de l'appel asynchrone que j'appelle? Sans pouvoir faire l'attente je perds le bénéfice d'être capable de céder cette exécution de threads. Je ne suis pas coincé avec Castle Windsor pour leur DynamicProxy alors je suis à la recherche d'un autre moyen de le faire. Nous avons étudié Unity, mais je ne veux pas remplacer toute notre implémentation AutoFac.

Toute aide serait appréciée.

Répondre

3

Tout cela est mauvais parce que nous ne faisons qu'échanger un thread pour un autre thread.

True. En outre, la version StartNew n'attend pas la fin de la méthode; il n'attendra que le premier await. Mais si vous ajoutez un Unwrap() pour le faire attendre la méthode complète, alors je soupçonne fortement que vous allez vous retrouver avec un blocage.

Le problème est que Castle.DynamicProxy IInterecptor ne le permet pas.

IInterceptor a une limitation de conception qu'il doit procéder de manière synchrone. Cela limite donc vos capacités d'interception: vous pouvez injecter du code synchrone avant ou après la méthode asynchrone, et du code asynchrone après la méthode asynchrone. Il n'y a aucun moyen d'injecter du code asynchrone avant la méthode asynchrone. C'est juste une limitation de DynamicProxy, un qui serait extrêmement pénible à corriger (comme dans, casse tous les code utilisateur existant).

Pour faire les types d'injection que est pris en charge, vous devez changer votre pensée un peu. L'un des modèles mentaux valides de async est qu'un Task renvoyé par une méthode représente l'exécution de cette méthode.Ainsi, pour ajouter du code à cette méthode, vous appelez la méthode directement, puis remplacez la valeur de retour de la tâche par une valeur augmentée.

Donc, quelque chose comme ça (pour les types de retour de 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); 
} 

Notez que la PreIntercept, la méthode interceptée et PostInterceptAsync sont tous exécutés dans le contexte d'origine (ASP.NET).

P.S. Une recherche rapide sur Google pour Async DynamicProxy a abouti à this. Cependant, je n'ai aucune idée de sa stabilité.

+1

Merci pour le lien je vais vérifier et je vais donner un exemple à votre exemple. J'ai cherché des réponses à cela pendant des jours. –