0

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

在单线程环境中,这样的工作:

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,这样它就不会在调用之间共享?

4

1 回答 1

3

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

当您像这样工作时,这意味着当您使用此拦截器来监视单例类(并跨线程使用)时,每个线程仍将获得自己的拦截器(当它注册为瞬态时),因为每次解析时都会得到一个新的拦截器(即使您重复使用相同的“拦截”实例)。

然而,这确实意味着你必须非常小心将这个被拦截的组件注入到哪里,因为如果你将这个被拦截的对象注入到另一个单例中,你将再次遇到麻烦。这种特殊的“麻烦”被称为俘虏依赖又名生活方式不匹配。很容易不小心错误地配置你的容器,从而让自己陷入麻烦,不幸的是,Ninject 缺乏警告你这一点的可能性。

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

// 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原则正确应用到您的设计时才可能使用装饰器,因为您通常需要有一些清晰的通用抽象才能将装饰器应用于系统中的大量类。在遗留代码库中有效地使用装饰器可能会让人望而生畏。

于 2014-10-18T10:20:27.837 回答