我尝试使用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,这样它就不会在调用之间共享?
答案 0 :(得分:3)
这在多线程应用程序中应该可以正常工作,因为每个线程都应该获得自己的对象图。因此,当您开始处理某个任务时,首先要解析一个新图形,并且图形不应该从一个线程传递给线程。这样可以将对线程安全(以及不是)集中的知识集中到应用程序中的一个地方,以便将所有内容连接起来:composition root。
当你像这样工作时,这意味着当你使用这个拦截器来监视单例类(并在线程中使用)时,每个线程仍将获得自己的拦截器(当它被注册为瞬态时),因为每次你解决你得到一个新的拦截器(即使你重复使用相同的'拦截'实例)。
但这确实意味着你必须非常小心地将这个截取的组件注入其中,因为如果你将这个截取的对象注入另一个单例,你将再次遇到麻烦。这种特殊的麻烦'被称为captive dependency a.k.a生活方式不匹配。很容易意外错误配置你的容器让你自己陷入麻烦,不幸的是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原则应用于您的设计时才能使用装饰器,因为您经常需要有一些明确的通用抽象,以便能够将装饰器应用于大范围的系统中的类。在传统的代码库中,我可能会有效地使用装饰器。