c# – 多线程代码使Rhino Mocks导致死锁
我们目前在单元测试中面临一些问题.我们的类是使用Rhino Mocks对Mocked对象的一些函数调用进行多线程处理.这是一个减少到最小值的例子:
public class Bar { private readonly List<IFoo> _fooList; public Bar(List<IFoo> fooList) { _fooList = fooList; } public void Start() { var allTasks = new List<Task>(); foreach (var foo in _fooList) allTasks.Add(Task.Factory.StartNew(() => foo.DoSomething())); Task.WaitAll(allTasks.ToArray()); } } 接口IFoo定义为: public interface IFoo { void DoSomething(); event EventHandler myEvent; } 为了重现僵局,我们的unittest将执行以下操作: [TestMethod] public void Foo_RaiseBar() { var fooList = GenerateFooList(50); var target = new Bar(fooList); target.Start(); } private List<IFoo> GenerateFooList(int max) { var mocks = new MockRepository(); var fooList = new List<IFoo>(); for (int i = 0; i < max; i++) fooList.Add(GenerateFoo(mocks)); mocks.ReplayAll(); return fooList; } private IFoo GenerateFoo(MockRepository mocks) { var foo = mocks.StrictMock<IFoo>(); foo.myEvent += null; var eventRaiser = LastCall.On(foo).IgnoreArguments().GetEventRaiser(); foo.DoSomething(); LastCall.On(foo).WhenCalled(i => eventRaiser.Raise(foo,EventArgs.Empty)); return foo; } 产生的Foo越多,死锁越频繁.如果测试不会阻止,运行它几次,它会的.
令我们最困惑的奇怪的事情是,拦截(…)方法的签名被定义为同步 – 但是几个线程位于这里.我读过几篇关于Rhino Mocks和Multithreaded的帖子,但没有发现警告(预期设置记录)或限制. [MethodImpl(MethodImplOptions.Synchronized)] public void Intercept(IInvocation invocation) 我们在设置我们的Mockobjects或在多线程环境中使用它们是否完全错误?欢迎任何帮助或暗示! 解决方法
这是您的代码中的竞争条件,而不是RhinoMock中的错误.当您在Start()方法中设置allTask??s任务列表时,会出现此问题:
public void Start() { var allTasks = new List<Task>(); foreach (var foo in _fooList) // the next line has a bug allTasks.Add(Task.Factory.StartNew(() => foo.DoSomething())); Task.WaitAll(allTasks.ToArray()); } 您需要将foo实例显式传递到任务中.该任务将在不同的线程上执行,并且很可能foreach循环将在任务开始之前替换foo的值. 这意味着每个foo.DoSomething()被调用有时从来没有,有时不止一次.因此,一些任务将无限期地阻止,因为RhinoMocks不能处理来自不同线程的同一实例上的事件的重叠提升,并且它陷入死锁. 在Start方法中替换此行: allTasks.Add(Task.Factory.StartNew(() => foo.DoSomething())); 有了这个: allTasks.Add(Task.Factory.StartNew(f => ((IFoo)f).DoSomething(),foo)); 这是一个经典的bug,它是微妙的,非常容易忽视的.它有时被称为“访问修改的关闭”. PS: 根据对这篇文章的评论,我用Moq重写了这个测试.在这种情况下,它不会阻止 – 但请注意,在给定实例上创建的期望可能不会被满足,除非原始错误是按照所述进行修复的.使用Moq的GenerateFoo()如下所示: private List<IFoo> GenerateFooList(int max) { var fooList = new List<IFoo>(); for (int i = 0; i < max; i++) fooList.Add(GenerateFoo()); return fooList; } private IFoo GenerateFoo() { var foo = new Mock<IFoo>(); foo.Setup(f => f.DoSomething()).Raises(f => f.myEvent += null,EventArgs.Empty); return foo.Object; } 它比RhinoMock更优雅,并且显然更容忍同时在同一个实例上提升事件的多个线程.虽然我并没有想到这是一个常见的要求 – 我个人并不常常会发现你可以假设一个事件的订阅者是线程安全的. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |