多线程环境下的Ninject拦截



我正试图使用Ninject.Extensions.Interception.DynmixProxy创建一个拦截器来记录方法的完成时间。

在单线程环境中,类似这样的操作是有效的:

public class TimingInterceptor : SimpleInterceptor
{
    readonly Stopwatch _stopwatch = new Stopwatch();
    private bool _isStarted;

    protected override void BeforeInvoke(IInvocation invocation)
    {
        _stopwatch.Restart();
        if (_isStarted) throw new Exception("resetting stopwatch for another invocation => false results");
        _isStarted = true;
        invocation.Proceed();
    }
    protected override void AfterInvoke(IInvocation invocation)
    {
        Debug.WriteLine(_stopwatch.Elapsed);
        _isStarted = false;
    }
}

然而,在多线程场景中,这将不起作用,因为StopWatch在调用之间是共享的。如何将StopWatch的实例从BeforeInvoke传递到AfterInvoke,使其不会在调用之间共享?

这在多线程应用程序中应该可以很好地工作,因为每个线程都应该获得自己的对象图。因此,当您开始处理某个任务时,首先要解析一个新的图,而图不应该在线程之间传递。这允许将什么是线程安全的(什么不是)知识集中到应用程序中连接所有内容的一个位置:组合根。

当你这样工作时,这意味着当你使用这个拦截器来监视单线程(并在线程之间使用)的类时,每个线程仍然会得到自己的拦截器(当它注册为瞬态时),因为每次你解析时,你都会得到一个新的拦截器(即使你重用了相同的"拦截"实例)。

然而,这意味着您必须非常小心地将这个被拦截的组件注入到其中,因为如果您将这个被截获的对象注入到另一个单例中,您将再次遇到麻烦。这种特殊的"麻烦"被称为圈养依赖,又称生活方式不匹配。很容易意外地误解你的容器,从而给自己带来麻烦,不幸的是,Ninject缺乏警告你这一点的可能性。

不过,请注意,如果您开始使用decorator而不是拦截器,您的问题就会消失,因为使用decorater,您可以将所有内容保存在一个方法中。这意味着即使是装饰器也可以是单例,而不会引起任何线程问题。示例:

// Timing cross-cutting concern for command handlers
public class TimingCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
{
    private readonly ICommandHandler<TCommand> decoratee;
    public TimingCommandHandlerDecorator(ICommandHandler<TCommand> decoratee)
    {
        this.decoratee = decoratee;
    }
    public void Handle(TCommand command)
    {
        var stopwatch = Stopwatch.StartNew();
        this.decoratee.Handle(command);
        Debug.WriteLine(stopwatch.Elapsed);
    }
}

当然,装饰器的使用通常只有在您将SOLID原则正确应用于设计时才有可能,因为您通常需要一些明确的通用抽象,才能将装饰器应用于系统中的大量类。在遗留代码库中高效地使用decorator可能会让人望而却步。

相关内容

  • 没有找到相关文章

最新更新