【问题标题】:Is it possible to create an async inteceptor using Castle.DynamicProxy?是否可以使用 Castle.DynamicProxy 创建异步拦截器?
【发布时间】:2017-01-27 14:34:36
【问题描述】:

我们基本上有一个如下所示的类,它使用 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 IIntereceptor 不允许这样做。我真的很想在拦截中等待。我可以做 .Result 但是我打电话的异步调用有什么意义呢?由于无法进行等待,我失去了它能够产生此线程执行的好处。我并没有因为他们的 DynamicProxy 而被 Castle Windsor 困住,所以我正在寻找另一种方法来做到这一点。我们已经研究了 Unity,但我不想替换我们的整个 AutoFac 实现。

任何帮助将不胜感激。

【问题讨论】:

    标签: asynchronous castle-dynamicproxy


    【解决方案1】:

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

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

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

    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) 上下文中运行。

    附:在 Google 上快速搜索异步 DynamicProxy 得到了this。不过,我不知道它有多稳定。

    【讨论】:

    • 感谢您的链接,我会检查一下,我会给你的例子。这几天我一直在寻找这个问题的答案。
    • 我刚刚查看了为什么 DynamicProxy 在Proceed 之前不允许异步代码,原因是传递给拦截器的调用会在拦截器返回。也就是说,如果拦截器设置为ReturnValueTask,它首先等待一些异步内容,然后然后 调用Proceed,那么到@987654336 时,调用将忘记下一步该做什么@ 由 Task 调用。这会导致整个调用从头开始重新开始,从而导致无限循环。可惜 - 如果调用使用不可变的数据结构,它会工作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-31
    • 2018-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多