我有一个应用内服务,它允许我从各种来源提供消息,这些消息将被放入一个简单的列表中。在自己的线程中运行的服务将定期将列表中的所有消息处理成各种文件;每个源的一个文件,然后按大小进行管理。
我的问题是关于检查邮件和围绕访问列表的代码执行锁定的正确方法。只有两个地方可以访问该列表;一个是将消息添加到列表中,另一个是将消息从列表中转储到处理列表中。
在列表中添加消息:
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...
据我所知,在这种特殊情况下,如果我进行一次双重检查可能并不重要,因为在处理函数之外,列表只能增长。但这是我的工作示例,我正在尝试了解线程的更精细细节。
提前感谢您的任何见解...
经过一些进一步的研究,谢谢你'浣熊'和一些实验性编程,我还有一些进一步的想法。
关于ReaderWriterLockSlim,我有以下示例似乎工作正常。它允许我读取列表中的消息数量,而不会干扰可能尝试读取列表中的消息数量或消息本身的其他代码。当我想要处理列表时,我可以将我的锁升级到写模式,将消息转储到处理列表中并在任何读/写锁之外处理它们,从而不会阻止任何其他可能想要添加或读取的线程,更多信息。
请注意,此示例使用更简单的构造作为消息,即String,而不是前一个使用Type和其他元数据的示例。
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
简洁本身......
答案 0 :(得分:3)
<强>理论值:强>
一旦线程获得_SyncLockObject
锁定,所有其他线程重新进入该方法将不得不等待锁定被释放。
因此锁定之前和之后的检查无关紧要。换句话说,它将没有任何效果。它也不安全,因为您没有使用并发列表。
如果一个线程在第一个测试中检查Count
而另一个线程正在清除或添加到集合中,那么您将获得Collection was modified; enumeration operation may not execute.
的异常。此外,第二次检查一次只能由一个线程执行(因为它已同步)。
这也适用于您的Add方法。虽然锁是由一个线程拥有的(意味着执行流已经到达该行),但是没有其他线程可以处理或添加到列表中。
如果您只是在应用程序中的某些其他位置读取列表,则应该小心锁定。对于更复杂的读/写方案(例如自定义并发收集),我建议使用ReaderWriterLockSlim
。
<强>实践:强>
使用BlockingCollection,因为它是线程安全的(即它在内部处理并发)。