2011-02-21 17 views
9

Şu anda Birim Testi sırasında bazı sorunlarla karşılaşıyoruz. Sınıfımız, Rhino Mocks kullanarak Mocked nesneler üzerinde bazı işlev çağrılarını multithreading. Çok iş parçacıklı kod, Rhino Mocks'ın bir Deadlock neden olmasına neden olur

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

Arayüz IFoo

olarak tanımlanır:

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

kilitlenme yeniden oluşturmak için bizim unittest şunları yapar: İşte minimuma indirgenmiş bir örnek 1. Bazı IFoo dalga geçer 2 oluşturun. DoSomething() çağrıldığında MyEvent öğesini kaldırın.

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

Daha fazla Foo üretilir, daha sık kilitlenme oluşur. Test engellenmezse, birkaç kez çalıştırın ve olacak. Bütün Görevler TaskStatus.Running hala ve mevcut çalışan iş parçacığı

de kırılma olduğunu, ayıklama testrun gösterileri durdurma
Rhino.Mocks.DLL! Rhino [uykuya olarak, bekleyin veya katılmak].

gerçeği bizi en karıştırır olan garip bir şey bayt 0x3d Mocks.Impl.RhinoInterceptor.Intercept (Castle.Core.Interceptor.IInvocation çağırma) +, o kesişimi imza (...) Yöntem Senkronize olarak tanımlanır - ancak birkaç konu burada bulunur. Rhino Mocks ve Multithreaded hakkında birkaç gönderi okudum, ancak uyarılar (kayıtların ayarlanması bekleniyor) veya sınırlamalar bulamadı.

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

Mockobject'leri ayarlamak veya bunları çok iş parçacıklı bir ortamda kullanmak konusunda tamamen yanlış bir şey yapıyor muyuz? Herhangi bir yardım veya ipucu açığız!

+1

Bunu buldum [http://blog.smithfamily.dk/post/2011/03/26/Thread-safe-version-of-Rhino-Mocks.aspx](http://blog.smithfamily.dk/ google seyahatlerimde/2011/03/26/Thread-safe-version-of-Rhino-Mocks.aspx). Ne yazık ki, orada barındırılan sürümde bir hata var gibi görünüyor, bu yüzden sorunu çözüp görmediğini göremiyorum. – jasper

cevap

12

Bu, kodunuzdaki bir yarış koşulu ve RhinoMocks'ta bir hata değil. Göreve içine açıkça foo örneğini geçmesi gerekiyor

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

: Eğer Start() yönteminde AllTasks görev listesi ayarlarken sorun oluşur. Görev farklı bir iş parçacığı üzerinde yürütülecek ve görev başlamadan önce foreach döngüsünün foo'nun değerinin yerini alması olasıdır.

Bu, her foo.DoSomething()'un bazen ve bazen birden çok kez çağrılmakta olduğu anlamına gelir. Bu nedenle, bazı görevler süresiz bir şekilde engellenecektir, çünkü RhinoMocks farklı örneklerden aynı olaydaki olayların örtüşmesini engelleyemez ve bir çıkmaz haline gelir.

sizin Start yönteminde bu satırı değiştirin: Bununla

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

:

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

Bu ince ve gözden kaçırmak çok kolay klasik bir hatadır. Bazen "değiştirilmiş bir kapanmaya erişim" olarak anılır.

Not: Bu yazıya yapılan yorumlar takiben

, ben MOQ kullanarak bu testi yeniden yazdım. Bu durumda engellenmez - ancak orijinal hata açıklanan şekilde düzeltilmedikçe belirli bir örnekte oluşturulan beklentilerin karşılanamayacağından emin olun. GenerateFoo() MOQ kullanarak şöyle görünür: Bu RhinoMocks daha zarif

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

- ve aynı anda aynı örneğinde olayları yükselterek çoklu iş parçacığı açıkça daha toleranslı. Her ne kadar bunun genel bir gereklilik olduğunu hayal etmese de, kişisel olarak, bir etkinliğe abone olanların iş parçacığı olduğunu kabul edebileceğiniz senaryoları bulamıyorum.

+1

+1 - evet bu bir foreach kapanma problemi (geçici bir değişkeni de yapardı) - ve 50 kere denilen tek bir foo olabilir - ama bu 'deadlock'u' getiriyor mu? Ben o tanıdık w/Rhino (alay bir alay :) olsa da, ama açıkça en azından (alay kodu) orada bir şey gibi görünmüyor. IMO, sadece bir foo'ya ateşlenen 50 olay olacak, ama yine de bitmek üzere görünüyor. – NSGaga

+1

Kilitlenme, bir kilitle eşdeğer olan SynchronizedAttribute ile işaretlenmiş RhinoInterceptor.Intercept yöntemindedir (bu). Aynı örnekte birden çok iş parçacığı çalıştırdığınızda kötü haberler. Hata ayıklayıcısındaki tüm çağrıları adım adım takip etmedim (tersine mühendislik RhinoMocks ve Castle'dan hoşlanmıyorum - bu kod kıllı!), Fakat Intercept'e yapılan çağrıların bir yığın içinde birden çok kez göründüğünü fark ettim. Intercept'ten farklı nesneler üzerinde geçmiş olan iki parçanın neden olduğu bir kilitlenme gibi görünüyor ve artık birbirlerini elde etmeye çalışan kilitler tutuyor. –

+0

bu daha mantıklı :), şimdi açık - ama yine de IMO'nun Rhino'nun tasarımının hatası - orada kilitleri haklı çıkaracak hiçbir şey yok (ne kadar anlayabildiğimi anlayabiliyorum) - el sanatları Mock (manuel sınıf) o w/o sorunları çalışması gerekir - Eğer yanlışı ile hata yani hata ile değil, sadece kaputun altında kaputu sorunları çözme ise sadece bu kaputu çözebilir ... Bu yüzden, belirli bir Q'nun cevabıdır - ama sonra tekrar Rhino ile ilgili olanın temizlenmesini istemişti) - kısa süre sonra tekrar tuzağa düşeceksin. Ben de hata ayıklamadım ... – NSGaga

2

Maggie, Görsel stüdyo varsa size yardımcı olabilir numuneden ama birşeyden bana bariz değil Ultimate ... Eğer kilitlenme sonra, daha sonra ayıklama menüsüne gidip seçmek ayıklayıcıya almak için tüm kırın:

Hata ayıklama -> Windows -> Paralel Yığınlar

Visual studio, çalışan tüm iş parçacıklarının durumunu gösteren güzel bir grafik oluşturur. Oradan genellikle hangi kilitlerin çekişmediğine dair bir ipucu var.

İlgili konular