2013-10-29 28 views
11

Mam pewne problemy z ReaderWriterLockSlim. Nie rozumiem, jak działa magia.ReaderWriterLockSlim i async czekajĘ ...

Mój kod:

private async Task LoadIndex() 
    { 
     if (!File.Exists(FileName + ".index.txt")) 
     { 
      return; 
     } 
     _indexLock.EnterWriteLock();// <1> 
     _index.Clear(); 
     using (TextReader index = File.OpenText(FileName + ".index.txt")) 
     { 
      string s; 
      while (null != (s = await index.ReadLineAsync())) 
      { 
       var ss = s.Split(':'); 
       _index.Add(ss[0], Convert.ToInt64(ss[1])); 
      } 
     } 
     _indexLock.ExitWriteLock();<2> 
    } 

Kiedy wprowadzić blokadę zapisu na < 1>, debugger widzę że _indexLock.IsWriteLockHeld jest true, ale kiedy kroki egzekucyjne do < 2> Widzę _indexLock.IsWriteLockHeld jest false i _indexLock.ExitWriteLock zgłasza wyjątek SynchronizationLockException z komunikatem "Blokada zapisu jest zwalniana bez trzymania". Co robię źle?

+0

W jaki sposób inicjalizowane jest '_indexLock'? Czy inny wątek może zainicjować go w tym samym czasie, gdy inny wątek jest w 'LoadIndex()'? –

+0

Może inny wątek, który ma dostęp do _indexLock, ponownie go inicjuje ... na pewno nie mógł zostać zwolniony przez inny wątek, ale może być ponownie zainicjowany na nowej instancji razem ... –

+0

Nie wymaga wątku, aby uzyskać _indexLock do nadpisania. –

Odpowiedz

24

ReaderWriterLockSlim jest typem blokującym wątek, więc zwykle nie można go używać z async i await.

Należy użyć SemaphoreSlim z WaitAsync, lub (jeśli naprawdę potrzebujesz czytnika/lock pisarz), używać mojego AsyncReaderWriterLock from AsyncEx lub Stephen Toub's AsyncReaderWriterLock.

+0

Czy nie byłoby również słuszne stwierdzenie, że to zależy od platformy? Kod opublikowany przez OP nie wspomina, czy jest to WPF czy aplikacja konsolowa. W aplikacji konsolowej nie działa, ponieważ nie ma tam "SynchronizationContext". Kod działa poprawnie w aplikacji WPF. http://stackoverflow.com/questions/15882710/is-it-safe-to-use-readerwriterlockslim-in-an-async-method?noredirect=1&lq=1 –

+0

@DonBox: Nie; jak zauważyłem w mojej odpowiedzi na to pytanie, nawet jeśli wznowisz pracę w tym samym wątku, nadal pozwalasz uruchomić dowolny kod podczas "trzymania" tej blokady. –

+0

Och, człowieku, i pomyślałem, że to dobrze. Czy możesz wyjaśnić, co masz na myśli przez "kod arbitralny"? Masz na myśli, że kod nie działa poprawnie, nawet w WPF? Co jest złego w utrzymywaniu tam blokady? W odpowiedzi, co rozumiesz przez "zwykle", "zwykle nie można go użyć"? Dzięki! –

0

Można bezpiecznie emulować mechanizm blokujący czytnik/program rejestrujący za pomocą niezawodnego i lekkiego modelu SemaphoreSlim i zachować korzyści z async/await. Utwórz SemaphoreSlim, podając liczbę dostępnych blokad odpowiadającą liczbie procedur, które zablokują jednocześnie Twój zasób do czytania. Każdy z nich zażąda jednego zamka, jak zwykle. W celu rutynowego pisania upewnij się, że żąda wszystkich dostępnych blokad przed wykonaniem jego czynności.

W ten sposób twoja procedura pisania zawsze będzie działać samodzielnie, podczas gdy twoje procedury czytania mogą dzielić zasoby tylko między sobą.

Załóżmy na przykład, że masz dwie procedury czytania i jedną procedurę zapisu.

SemaphoreSlim semaphore = new SemaphoreSlim(2); 

async void Reader1() 
{ 
    await semaphore.WaitAsync(); 
    try 
    { 
     // ... reading stuff ... 
    } 
    finally 
    { 
     semaphore.Release(); 
    } 
} 

async void Reader2() 
{ 
    await semaphore.WaitAsync(); 
    try 
    { 
     // ... reading other stuff ... 
    } 
    finally 
    { 
     semaphore.Release(); 
    } 
} 

async void ExclusiveWriter() 
{ 
    // the exclusive writer must request all locks 
    // to make sure the readers don't have any of them 
    // (I wish we could specify the number of locks 
    // instead of spamming multiple calls!) 
    await semaphore.WaitAsync(); 
    await semaphore.WaitAsync(); 
    try 
    { 
     // ... writing stuff ... 
    } 
    finally 
    { 
     // release all locks here 
     semaphore.Release(2); 
     // (oh here we don't need multiple calls, how about that) 
    } 
} 

Oczywiście ta metoda działa tylko wtedy, gdy wiesz wcześniej, ile procedur czytania można uruchomić w tym samym czasie. Wprawdzie zbyt wiele z nich sprawiłoby, że kod ten byłby brzydki.

+1

To nie zapewnia semantyki zapisu/zapisu. Idea blokady czytnika/pisarza polega na tym, że dowolna liczba współbieżnych czytników może być dowolna, ale gdy trwa operacja zapisu, nie może być żadnych czytników ani innych pisarzy. To zarówno niepotrzebnie ogranicza czytelników, jak i nie właściwie ogranicza czytelników lub innych autorów podczas pisania jednej operacji. – Servy

+0

Zablokowuję wszelkie programy piszące, gdy masz aktywne czytniki, i nadal zezwala na współbieżne czytniki, gdy nie masz aktywnych pisarzy. Zasadniczo jest to zadanie, które powinien wykonać blokujący czytelnik/pisarz, z wyjątkiem sytuacji, gdy nie wiesz, ilu czytelników posiadasz, jak to ** wyraźnie podkreśliłem (nawet, jeśli można o tym pomyśleć, ale Nie zepsuje dla ciebie niespodzianki). Może również nie być tak elegancka jak na twoje standardy, ale to ** jest poprawnym rozwiązaniem i sam użyłem tego w dobrze przetestowanych, dobrze obciążonych, wymagających usługach. Chodzi mi o to, by być prostym, ale każdy dla siebie ... – mycelo

+0

To nie jest złe dla szybkiego rozwiązania, które nie zależy od zewnętrznych zależności. Powinien być prawdopodobnie zawinięty w swoją własną klasę do ponownego wykorzystania w razie potrzeby (wywołanie wielu '.WaitAsync()' dla każdego odczytu, a także pamiętanie, ile czytelników rozliczałeś przy każdym użyciu i zmienianie tego przy każdym użyciu, gdy jest to konieczne, byłoby brzydkie i podatne na błędy). –