簡単なリストに入れられるさまざまなソースからのメッセージをフィードできるアプリケーション内サービスがあります。独自のスレッドで実行されるサービスは、定期的に、リスト内のすべてのメッセージをさまざまなファイルに処理します。ソースごとに 1 つのファイルが作成され、サイズが管理されます。
私の質問は、メッセージをチェックし、リストにアクセスするコードをロックする適切な方法についてです。リストにアクセスできる場所は 2 つだけです。1 つはメッセージがリストに追加される場所で、もう 1 つはメッセージがリストから処理リストにダンプされる場所です。
リストへのメッセージの追加:
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
リストの処理:
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...
私の質問は次のとおりです。リストを処理しているときに二重チェックを行う必要がありますか?以下を参照してください。なぜ?または、なぜですか?また、ロックの外でリストの count プロパティにアクセスすることに危険はありますか? どちらの方法がより優れているか、またはより効率的ですか? なぜ?または、なぜですか?
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...
この特定のケースでは、処理関数の外ではリストが大きくなるだけであるという事実を考慮して、二重チェックを行っても問題ない場合があることを理解しています。しかし、これは私の実際の例であり、スレッド化の詳細について学ぼうとしています。
洞察をお寄せいただきありがとうございます…</p>
いくつかのさらなる調査の後、「クーン」に感謝し、いくつかの実験的なプログラミングを行った後、さらにいくつかの考えがあります。
ReaderWriterLockSlimに関しては、うまくいくように見える次の例があります。リスト内のメッセージ数やメッセージ自体を読み取ろうとしている可能性のある他のコードに干渉することなく、リスト内のメッセージ数を読み取ることができます。また、リストを処理したい場合は、ロックを書き込みモードにアップグレードし、メッセージを処理リストにダンプして、読み取り/書き込みロックの外側で処理できるため、追加または読み取りが必要な他のスレッドをブロックしません。 、その他のメッセージ。
この例では、他のメタデータとともに Type を使用した前の例とは対照的に、メッセージに単純な構造体である String を使用していることに注意してください。
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
この手法に関する唯一の問題は、開発者がロックの取得と解放に熱心に取り組む必要があることです。そうしないと、デッドロックが発生します。
これがSyncLockを使用するよりも効率的かどうかについては、私は本当に言えませんでした。この特定の例とその使用法については、どちらでも十分だと思います。他の誰かがカウントを変更している間にカウントを読み取ることについて「クーン」が与えたまさにその理由について、私は二重チェックを行いません。この例では、SyncLockは同じ機能を提供します。ただし、複数のソースがリストに対して読み取りと書き込みを行う可能性がある、もう少し複雑なシステムでは、ReaderWriterLockSlimが理想的です。
BlockingCollectionリストに関して、次の例は上記のように機能します。
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
シンプルさそのもの…</p>