是否可以使用Castle.DynamicProxy创建异步交互?

时间:2016-09-19 19:53:33

标签: asynchronous castle-dynamicproxy

我们基本上有一个如下所示的类,使用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()

所有这些都很糟糕,因为我们实际上只是将一个线程换成另一个线程。我真的很想改变

的签名
void IInterceptor.Intercept(IInvocation invocation)

async Task IInterceptor.Intercept(IInvocation invocation)

摆脱Task.Run或Task.Factory,然后制作它:

await InterceptAsync(invocation, taskWrapper);

问题是Castle.DynamicProxy IInterecptor不允许这样做。我真的想在拦截中等待。我可以做.Result但是那时我调用的异步调用的重点是什么?如果没有能够做到等待,我就失去了能够产生这个线程执行的好处。我没有被Castle Windsor用于他们的DynamicProxy,所以我正在寻找另一种方法来做到这一点。我们已经研究过Unity,但我不想替换整个AutoFac实现。

任何帮助都将不胜感激。

1 个答案:

答案 0 :(得分:6)

  

所有这些都很糟糕,因为我们实际上只是将一个线程换成另一个线程。

真。另外因为StartNew版本实际上并没有等待方法完成;它只会等到第一个await。但是如果你添加一个Unwrap()来让它等待完整的方法,那么我强烈怀疑你最终会陷入僵局。

  

问题是Castle.DynamicProxy IInterecptor不允许这样做。

IInterceptor确实存在设计限制,必须同步进行。因此,这限制了您的拦截功能:您可以在异步方法之前或之后注入同步代码,并在异步方法之后注入异步代码。在异步方法之前无法注入异步代码。这只是DynamicProxy的一个限制,一个非常难以纠正(例如,打破所有现有用户代码)。

要进行 支持的注入,你必须改变一下你的想法。 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搜索异步DynamicProxy会产生this。不过,我不知道它有多稳定。