2012-07-03 18 views
5

Mam usługę w aplikacji, która pozwala mi przekazywać wiadomości z różnych źródeł, które zostaną umieszczone na prostej liście. Usługa, działająca we własnym wątku, będzie okresowo przetwarzać wszystkie wiadomości z listy na różne pliki; jeden plik dla każdego źródła, którym następnie zarządza się pod względem wielkości.Czy należy dwukrotnie sprawdzić przed i po zablokowaniu listy?

Moje pytanie dotyczy właściwego sprawdzania wiadomości i wykonywania blokady wokół kodu, który uzyskuje dostęp do listy. Są tylko dwa miejsca, które mają dostęp do listy; jeden to miejsce, w którym wiadomość jest dodawana do listy, a druga to miejsce, w którym wiadomości są zrzucane z listy na listę przetwarzania.

Dodawanie wiadomość do listy:

Public Sub WriteMessage(ByVal messageProvider As IEventLogMessageProvider, ByVal logLevel As EventLogLevel, ByVal message As String) 
    SyncLock _SyncLockObject 
     _LogMessages.Add(New EventLogMessage(messageProvider, logLevel, Now, message)) 
    End SyncLock 
End Sub 

tworzenie listy:

Dim localList As New List(Of EventLogMessage) 
SyncLock _SyncLockObject 
    If (_LogMessages.Count > 0) Then 
     localList.AddRange(_LogMessages) 
     _LogMessages.Clear() 
    End If 
End SyncLock 

' process list into files... 

Moje pytania to: zrobić podwójne sprawdzanie kiedy jestem przetwarzanie listy, patrz niżej? I dlaczego? A może nie? Czy są jakieś niebezpieczeństwa związane z dostępem do własności licznika listy poza blokadą? Czy któraś z metod jest lepsza lub bardziej wydajna? I dlaczego? A może nie?

Dim localList As New List(Of EventLogMessage) 
If (_LogMessages.Count > 0) Then 
    SyncLock _SyncLockObject 
     If (_LogMessages.Count > 0) Then 
      localList.AddRange(_LogMessages) 
      _LogMessages.Clear() 
     End If 
    End SyncLock 
End If 

' process list into files... 

Rozumiem, że w tym konkretnym przypadku, to może nie ma znaczenia, czy robię podwójną kontrolę Biorąc pod uwagę fakt, że poza funkcją przetwarzania, lista może tylko rosnąć. Ale to jest mój przykład pracy i staram się poznać dokładniejsze szczegóły wątków.

Z góry dziękuję za wszelkie spostrzeżenia ...


Po kilku dalszych badań, dziękuję „na coon”, a niektóre programy eksperymentalne, mam kilka dalszych myśli.

W odniesieniu do ReaderWriterLockSlim, mam następujący przykład, który wydaje się działać dobrze. Pozwala mi to odczytać liczbę wiadomości na liście bez ingerencji w inny kod, który może próbować odczytać liczbę wiadomości z listy lub same wiadomości. A gdy chcę przetworzyć listę, mogę uaktualnić tryb blokady do zapisu, zrzucić wiadomości na listę przetwarzania i przetworzyć je poza blokadami odczytu/zapisu, nie blokując żadnych innych wątków, które mogą chcieć dodać lub przeczytać , więcej wiadomości.

Należy zauważyć, że w tym przykładzie użyto prostszej konstrukcji dla komunikatu, String, w przeciwieństwie do poprzedniego przykładu, w którym użyto typu wraz z kilkoma innymi metadanymi.

Private _ReadWriteLock As New Threading.ReaderWriterLockSlim() 

Private Sub Process() 
    ' create local processing list 
    Dim processList As New List(Of String) 
    Try 
     ' enter read lock mode 
     _ReadWriteLock.EnterUpgradeableReadLock() 
     ' if there are any messages in the 'global' list 
     ' then dump them into the local processing list 
     If (_Messages.Count > 0) Then 
      Try 
       ' upgrade to a write lock to prevent others from writing to 
       ' the 'global' list while this reads and clears the 'global' list 
       _ReadWriteLock.EnterWriteLock() 
       processList.AddRange(_Messages) 
       _Messages.Clear() 
      Finally 
       ' alway release the write lock 
       _ReadWriteLock.ExitWriteLock() 
      End Try 
     End If 
    Finally 
     ' always release the read lock 
     _ReadWriteLock.ExitUpgradeableReadLock() 
    End Try 
    ' if any messages were dumped into the local processing list, process them 
    If (processList.Count > 0) Then 
     ProcessMessages(processList) 
    End If 
End Sub 

Private Sub AddMessage(ByVal message As String) 
    Try 
     ' enter write lock mode 
     _ReadWriteLock.EnterWriteLock() 
     _Messages.Add(message) 
    Finally 
     ' always release the write lock 
     _ReadWriteLock.ExitWriteLock() 
    End Try 
End Sub 

Jedyny problem, jaki widzę przy użyciu tej techniki, to to, że deweloper musi być sumienny w kwestii pozyskiwania i zwalniania zamków. W przeciwnym razie wystąpią zakleszczenia.

Jeśli chodzi o to, czy jest to bardziej efektywne niż użycie SyncLock, naprawdę nie mogłem powiedzieć. W tym konkretnym przykładzie i jego zastosowaniu uważam, że albo wystarczy. Nie zrobiłbym podwójnego sprawdzenia z tych samych powodów, dla których "Coon" dał o przeczytaniu zliczenia, podczas gdy ktoś inny to zmienia. Biorąc pod uwagę ten przykład, SyncLock zapewnia taką samą funkcjonalność. Jednak w nieco bardziej złożonym systemie, w którym wiele źródeł może odczytywać i zapisywać na liście, idealne byłoby urządzenie ReaderWriterLockSlim.


W przypadku listy BlockingCollection następujący przykład działa jak wyżej.

Private _Messages As New System.Collections.Concurrent.BlockingCollection(Of String) 

Private Sub Process() 
    ' process each message in the list 
    For Each item In _Messages 
     ProcessMessage(_Messages.Take()) 
    Next 
End Sub 

Private Sub AddMessage(ByVal message As String) 
    ' add a message to the 'global' list 
    _Messages.Add(message) 
End Sub 

sama prostota ...

+0

Jaką kontrolę masz nad architekturą usługi? Wydaje się, że wiele ciężkich uniesień można było zrobić przy użyciu MSMQ. http://msdn.microsoft.com/en-us/library/ms789048.aspx – Trevor

+0

Pozwolę sobie wyjaśnić, kiedy mówię o usłudze, mam na myśli klasę wewnętrzną aplikacji, która jest uruchamiana na początku aplikacji, tworzy wątek do okresowego przetwarzania komunikatów nadanych mu przez inne części aplikacji, a nie usługę Windows. Uogólniona obsługa komunikatów, jeśli zajdzie taka potrzeba, zamknięta całkowicie w aplikacji, działająca na własnym wątku tła. Ta "usługa" ma za zadanie 1) zapewnienie prostego sposobu na tworzenie wiadomości oraz 2) zapobieganie przechowywaniu wiadomości w zbyt małym miejscu. Nie ma również potrzeby przesyłania komunikatów między aplikacjami. –

+0

Wiadomość może być niewłaściwą terminologią dla tej aplikacji. To nie tyle wiadomość, co rejestrowane zdarzenie. Zdarzenie "debugowania", "komunikatu", "ostrzeżenia" lub "błędu", które jest specyficzne i istotne tylko dla aplikacji lub modułu, który utworzył wiadomość. –

Odpowiedz

3

Teoria:

Po nitki nabywa _SyncLockObject zablokować wszystkie inne wątki reentering tej metody będzie musiał czekać na zwolnienie blokady.

Dlatego kontrola przed i po zamku jest nieistotna. Innymi słowy, nie przyniesie to efektu. Jest również niezabezpieczony , ponieważ nie używasz listy współbieżnej.

Jeśli jeden wątek zostanie sprawdzony pod kątem Count w pierwszym teście, podczas gdy inny będzie czyszczony lub dodawany do kolekcji, otrzymasz wyjątek z Collection was modified; enumeration operation may not execute.. Druga kontrola może być wykonywana tylko przez jeden wątek na raz (ponieważ jest zsynchronizowany).

Dotyczy to również metody Dodaj. Chociaż blokada jest własnością jednego wątku (co oznacza, że ​​przepływ wykonania osiągnął tę linię), żadne inne wątki nie będą mogły przetworzyć ani dodać do listy.

Należy również zachować ostrożność, aby również zablokować, jeśli czytasz tylko z listy w niektórych innych miejscach w aplikacji. W przypadku bardziej złożonych scenariuszy odczytu/zapisu (takich jak niestandardowe zbieżne zbierania), zalecam użycie ReaderWriterLockSlim.

Praktyka:

Użyj BlockingCollection, ponieważ jest to bezpieczne wątek (tj obsługuje współbieżność wewnętrznie).

Powiązane problemy