2013-06-11 9 views
11

Aby zsynchronizować dostęp do moich usług, używam klasy ReaderWriterLockSlim. Używam następującego kodu, aby uzyskać dostęp do moich właściwości w sposób bezpieczny dla wątków.Uwaga firmy Microsoft do ReaderWriterLockSlim.IsReadLockHeld/IsWriteLockHeld i jej konsekwencje

public class SomeClass 
{ 
    public readonly ReaderWriterLockSlim SyncObj = new ReaderWriterLockSlim(); 
    public string AProperty 
    { 
     get 
     { 
      if (SyncObj.IsReadLockHeld) 
       return ComplexGetterMethod(); 
      SyncObj.EnterReadLock(); 
      try 
      { 
       return ComplexGetterMethod(); 
      } 
      finally 
      { 
       SyncObj.ExitReadLock(); 
      } 
     } 
     set 
     { 
      if (SyncObj.IsWriteLockHeld) 
       ComplexSetterMethod(value); 
      else 
      { 
       SyncObj.EnterWriteLock(); 
       ComplexSetterMethod(value); 
       SyncObj.ExitWriteLock(); 
      } 
     } 
    } 

    // more properties here ... 

    private string ComplexGetterMethod() 
    { 
     // This method is not thread-safe and reads 
     // multiple values, calculates stuff, ect. 
    } 

    private void ComplexSetterMethod(string newValue)  
    { 
     // This method is not thread-safe and reads 
     // and writes multiple values. 
    } 
} 

// ===================================== 

public static SomeClass AClass = new SomeClass(); 
public void SomeMultiThreadFunction() 
{ 
    ... 
    // access with locking from within the setter 
    AClass.AProperty = "new value"; 
    ... 
    // locking from outside of the class to increase performance 
    AClass.SyncObj.EnterWriteLock(); 
    AClass.AProperty = "new value 2"; 
    AClass.AnotherProperty = "..."; 
    ... 
    AClass.SyncObj.ExitWriteLock(); 
    ... 
} 

Aby uniknąć niepotrzebnych blokad ilekroć pobrać lub ustawić wiele właściwości raz opublikowałem ReaderWriterLockSlim -przedmiot i zablokować je od zewnątrz klasy każdym razem mam zamiar pobrać lub ustawić kilka właściwości. Aby to osiągnąć, moje metody pobierające i ustawiające sprawdzają, czy blokada została uzyskana przy użyciu właściwości IsReadLockHeld i właściwości IsWriteLockHeld z ReaderWriterLockSlim. Działa to dobrze i zwiększyło wydajność mojego kodu.

Jak na razie dobrze, ale kiedy ponownie przeczytać dokumentację o IsReadLockHeld i IsWriteLockHeld Zauważyłem formularz uwaga Microsoft:

Ta właściwość jest przeznaczony do stosowania u twierdzi, lub do innych celów debugowania . Nie używaj go do kontrolowania przepływu programu.

Moje pytanie brzmi: Czy istnieje powód, dlaczego nie powinno się używać IsReadLockHeld/IsWriteLockHeld na ten cel? Czy coś jest nie tak z moim kodem? Wszystko działa zgodnie z oczekiwaniami i znacznie szybciej niż przy użyciu blokad rekursywnych (LockRecursionPolicy.SupportsRecursion).

Aby to wyjaśnić: To jest minimalny przykład. Nie chcę wiedzieć, czy sam zamek jest konieczny, czy można go usunąć lub osiągnąć w inny sposób. Chcę tylko wiedzieć, dlaczego nie powinienem używać IsReadLockHeld/IsWriteLockHeld, aby kontrolować przepływ programu zgodnie z dokumentacją.

+0

Czytnik ReaderWriteLockSlim niczego nie chroni, .NET obiecuje już, że aktualizacje odniesień do obiektów są atomowe. Jedynym łagodnym efektem ubocznym będzie to, że aktualizacje ustawiacza będą widoczne w innych wątkach. Znacznie taniej jest po prostu użyć MemoryBarrier(). Żadnych gwarancji bezpieczeństwa nici, na pewno lepiej usunąć całkowicie SyncObj, ponieważ daje tylko fałszywe poczucie bezpieczeństwa. –

+0

@EricLippert: Dokumenty dla 'IsRead/WriteLockHeld' sugerują, że zwracają' true', gdy * bieżący wątek * trzyma blokadę. Jeśli 'IsRead/WriteLockHeld' zwraca wartość true, jak może zmienić się stan blokady w tym wątku bez jawnego wywoływania' ExitRead/WriteLock'? – Iridium

+0

@Iridium: Wycofuję komentarz; Obecnie nie jestem pewien, dlaczego dokumentacja powoduje takie zastrzeżenie. Powiedział: najlepszą praktyką jest robienie tego, co mówi dokumentacja. –

Odpowiedz

12

Po dalszych badaniach opublikowałem to samo pytanie na German Support Forum of the Microsoft Developer Network i rozpocząłem dyskusję z bardzo pomocnym moderatorem Marcel Roma. Był w stanie skontaktować się z programatora ReaderWriterLockSlimJoe Duffy kto napisał tę odpowiedź:

Obawiam się, moja odpowiedź może pozostawiać wiele do życzenia.

Właściwość działa poprawnie i zgodnie z dokumentacją. Poradnik naprawdę jest po prostu , ponieważ warunkowe pozyskiwanie i zwalnianie blokad jest często błędne i podatne na błędy w praktyce, w szczególności z wyjątkami wrzuconymi do mieszanki .

Zazwyczaj dobrze jest ustrukturyzować swój kod, aby uzyskać rekurencyjny nabytek, lub nie, (i oczywiście ten ostatni jest zawsze łatwiejszy do uzasadnienia); używanie właściwości takich jak IsReadLockHeld wyląduje gdzieś pośrodku.

Byłem jednym z głównych projektantów RWLS i muszę przyznać, że ma zbyt wiele dzwonków i gwizdków. Nie koniecznie muszę żałować dodania IsReadLockHeld - ponieważ może się przydać do debugowania i zapewnień - jednak jak tylko dodaliśmy to, pudełko Pandory zostało otwarte, a my RWLS natychmiast otworzyliśmy się na tego rodzaju użycie.

Nie jestem zaskoczony, że ludzie chcą go używać, jak pokazano w wątku StackOverflow i jestem pewien, że istnieją pewne uzasadnione scenariusze , gdzie działa lepiej niż alternatywy. Po prostu nie radzę sobie z tym, że nie używam go po stronie .

Podsumowując rzeczy: Można użyć i właściwość IsReadLockHeldIsWriteLockHeld uzyskania blokady warunkowo i wszystko będzie działać dobrze, ale to jest zły styl programowania i należy go unikać. Lepiej trzymać się rekursywnych lub nierekurencyjnych zamków. Aby zachować dobry styl kodowania IsReadLockHeld i IsWriteLockHeld należy używać wyłącznie do celów debugowania.

Chcę ponownie podziękować Marcelowi Roma i Joe Duffy za ich cenną pomoc.

-2

Dokumentacja doradza Ci właściwą rzecz.

Należy rozważyć następujące przeplecione wykonanie.

Thread1.AcqrireReadLock(); 
Thread1.ComplexGetterMethod(); 
Thread2.ReadIsReaderLockHeldProperty(); 
Thread1.ReleaseReadLock(); 
Thread2.ComplexGetterMethod(); // performing read without lock. 

Drugą rzeczą źle z kodem, który widzę jest

SyncObj.EnterReadLock(); 
try 
{ 
    return ComplexGetterMethod(); 
} 
finally 
{ 
    SyncObj.ExitReadLock(); 
} 

nie jest właściwa droga do rzeczy. Jest to jedno prawo:

try 
{ 
    SyncObj.EnterReadLock(); 

    return ComplexGetterMethod(); 
} 
finally 
{ 
    if (SyncObj.IsReadLockHeld) 
     SyncObj.ExitReadLock(); 
} 

I to będzie dokładna definicja twojej metody gettera.

+0

To wykonanie nie nastąpiłoby w ten sposób, ponieważ 'IsReadLockHeld' zwraca' true' tylko wtedy, gdy ** bieżący wątek ** przechowuje blokadę. – svick

+0

Mój błąd. Masz rację. – Egor

Powiązane problemy