我正试图使用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可能会让人望而却步。