2011-02-21 34 views
9

Hiện chúng tôi đang gặp phải một số vấn đề trong khi kiểm tra Đơn vị. Lớp của chúng ta đa luồng một số lời gọi hàm trên các đối tượng Mocked bằng cách sử dụng Rhino Mocks. Dưới đây là một ví dụ giảm xuống mức tối thiểu:Mã đa luồng làm cho Rhino Mocks gây ra một Deadlock

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()); 
    } 
} 

Giao diện IFoo được định nghĩa là:

public interface IFoo 
{ 
    void DoSomething(); 
    event EventHandler myEvent; 
} 

Để tái tạo các bế tắc, unittest của chúng tôi thực hiện như sau: 1. tạo ra một số IFoo Mocks 2. Tăng myEvent khi DoSomething() được gọi.

[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; 
    } 

Càng nhiều Foo được tạo, càng thường xảy ra bế tắc. Nếu kiểm tra sẽ không chặn, hãy chạy nó nhiều lần, và nó sẽ. Dừng gỡ lỗi chương trình testrun, rằng tất cả các nhiệm vụ vẫn còn trong TaskStatus.Running và các sợi nhân hiện nay là vi phạm tại

[Trong một giấc ngủ, chờ đợi, hoặc tham gia]
Rhino.Mocks.dll! Rhino. Mocks.Impl.RhinoInterceptor.Intercept (Castle.Core.Interceptor.IInvocation gọi) + 0x3d byte

điều lạ mà confuses chúng tôi hầu hết là một thực tế, rằng chữ ký của Intercept (...) Phương pháp được định nghĩa là Synchronized - nhưng một số Threads được đặt ở đây. Tôi đã đọc một số thông tin về Rhino Mocks và Multithreaded, nhưng không tìm thấy cảnh báo (dự kiến ​​thiết lập các bản ghi) hoặc các hạn chế.

[MethodImpl(MethodImplOptions.Synchronized)] 
    public void Intercept(IInvocation invocation) 

Chúng tôi đang làm điều gì đó hoàn toàn sai khi thiết lập Mockobject hoặc sử dụng chúng trong môi trường đa luồng? Bất kỳ trợ giúp hoặc gợi ý được hoan nghênh!

+1

Tôi thấy điều này [http://blog.smithfamily.dk/post/2011/03/26/Thread-safe-version-of-Rhino-Mocks.aspx](http://blog.smithfamily.dk/ post/2011/03/26/Thread-safe-version-of-Rhino-Mocks.aspx) trên các chuyến đi google của tôi. Đáng buồn thay, phiên bản được lưu trữ ở đó dường như có một lỗi trong đó, vì vậy tôi không thể nhìn thấy nếu nó sửa vấn đề. – jasper

Trả lời

12

Đây là tình trạng chủng tộc trong mã của bạn chứ không phải lỗi trong RhinoMocks. Vấn đề xảy ra khi bạn đang thiết lập danh sách công việc allTasks trong phương pháp Start():

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()); 
} 

Bạn cần phải vượt qua dụ foo rõ ràng thành nhiệm vụ. Nhiệm vụ sẽ thực hiện trên một luồng khác và rất có khả năng vòng lặp foreach sẽ thay thế giá trị của foo trước khi tác vụ đã bắt đầu.

Điều này có nghĩa là mỗi foo.DoSomething() đang được gọi đôi khi không bao giờ và đôi khi nhiều lần. Vì lý do này, một số tác vụ sẽ chặn vô thời hạn vì RhinoMocks không thể xử lý việc chồng lên nhau các sự kiện trên cùng một thể hiện từ các luồng khác nhau và nó bị bế tắc.

Thay thế dòng này trong phương pháp Start của bạn:

allTasks.Add(Task.Factory.StartNew(() => foo.DoSomething())); 

Với điều này:

allTasks.Add(Task.Factory.StartNew(f => ((IFoo)f).DoSomething(), foo)); 

Đây là một lỗi cổ điển đó là tinh tế và rất dễ dàng để bỏ qua. Nó đôi khi được gọi là "truy cập một đóng cửa sửa đổi".

PS:

Sau khi bình luận về bài đăng này, tôi viết lại bài kiểm tra này sử dụng Moq. Trong trường hợp này, nó không chặn - nhưng hãy cẩn thận rằng các kỳ vọng được tạo trên một cá thể cụ thể có thể không được thỏa mãn trừ khi lỗi ban đầu được sửa như mô tả. GenerateFoo() bằng cách sử dụng Moq trông giống như sau:

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; 
} 

Nó thanh lịch hơn RhinoMocks - và rõ ràng khoan dung nhiều chủ đề nâng cao sự kiện trên cùng một trường hợp cùng một lúc. Mặc dù tôi không tưởng tượng đây là một yêu cầu phổ biến - cá nhân tôi không thường xuyên tìm thấy các kịch bản mà bạn có thể giả định người đăng ký một sự kiện là an toàn chỉ.

+1

+1 - có đó là một vấn đề đóng cửa foreach (một biến temp sẽ làm quá) - và bạn có thể chỉ có một foo được gọi là 50 lần - nhưng không 'mang lại' bế tắc? Tôi không quen thuộc với w/tê giác (mặc dù giả là một mô hình :) nhưng không giống như bất cứ điều gì có khối, rõ ràng ít nhất (mã giả). IMO bạn sẽ nhận được 50 sự kiện được kích hoạt chỉ một lần, nhưng vẫn có vẻ được thiết lập để kết thúc. – NSGaga

+1

Deadlock là phương pháp RhinoInterceptor.Intercept được đánh dấu bằng SynchronizedAttribute, tương đương với khóa (this). Tin xấu khi bạn đã có nhiều luồng thực hiện trên cùng một cá thể. Tôi đã không theo chuỗi toàn bộ các cuộc gọi trong bước gỡ lỗi từng bước (tôi không ưa thích kỹ thuật đảo ngược RhinoMocks và Castle - mã đó là lông!), Nhưng tôi phát hiện ra rằng các cuộc gọi đến Intercept xuất hiện nhiều lần trong một ngăn xếp - vì vậy trông giống như một bế tắc gây ra bởi hai chủ đề đã đi qua đánh chặn một lần trên các đối tượng khác nhau và đang nắm giữ ổ khóa bây giờ cố gắng để có được mỗi người khác '. –

+0

làm cho ý nghĩa hơn :), bây giờ nó rõ ràng - nhưng nó vẫn IMO lỗi của thiết kế của tê giác - như không có gì trong đó biện minh cho ổ khóa (mặc dù tôi có thể hiểu làm thế nào người ta có thể đến đó dễ dàng) - tức là nếu bạn thủ công các Mock (lớp thủ công) nó sẽ làm việc w/o vấn đề - nếu tôi không nhầm tức là lỗi với đóng cửa chỉ là đưa lên những vấn đề nằm dưới mui xe ... trong khi đó có thể giải quyết khóa (do đó, nó là một câu trả lời cho Q cụ thể - nhưng sau đó một lần nữa ai đó muốn xóa này liên quan đến Rhino dường như) - bạn sẽ bị mắc kẹt sớm một lần nữa. Tôi cũng không gỡ lỗi như vậy ... – NSGaga

2

Maggie, Không rõ ràng đối với tôi từ mẫu nhưng có thể giúp bạn nếu bạn có Visual studio Ultimate ... Khi bạn bế tắc, hãy ngắt tất cả để vào trình gỡ lỗi, sau đó đi tới trình đơn Debug và chọn:

Gỡ lỗi -> Windows -> Ngăn xếp song song

Visual studio tạo biểu đồ đẹp cho biết trạng thái của tất cả các chuỗi đang chạy. Từ đó bạn thường nhận được một số loại gợi ý là khóa nào đang tranh chấp.

Các vấn đề liên quan