我有一个应用程序内服务,它允许我向它提供来自各种来源的消息,这些消息将被放入一个简单的列表中。该服务在自己的线程中运行,将定期将列表中的所有消息处理成各种文件;每个源一个文件,然后对其大小进行管理。
我的问题是关于检查消息和对访问列表的代码执行锁定的正确方法。只有两个地方可以访问该列表;一种是将消息添加到列表的位置,另一种是将消息从列表转储到处理列表中的位置。
将消息添加到列表:
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...
我的问题是:我在处理列表时是否应该仔细检查,见下文?为什么?或者为什么不呢?在锁之外访问列表的计数属性是否有任何危险?哪种方法更好或更有效?为什么?或者为什么不呢?
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,我有以下示例,它似乎工作正常。它允许我读取列表中的消息数量,而不会干扰可能试图读取列表中消息数量或消息本身的其他代码。当我想要处理列表时,我可以将我的锁升级为写入模式,将消息转储到处理列表中并在任何读/写锁之外处理它们,因此不会阻塞任何其他可能想要添加或读取的线程, 更多消息。
请注意,这个例子使用了一个更简单的消息结构,一个字符串,而不是前面的例子,它使用了一个类型和一些其他元数据。
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
Simplicity itself…</p>