Je dispose d'un service intégré à l'application qui me permet de l'alimenter en messages provenant de diverses sources, qui seront placés dans une simple liste. Le service, qui s'exécute dans son propre thread, traitera périodiquement tous les messages de la liste dans différents fichiers, un fichier pour chaque source, qui seront ensuite gérés en fonction de leur taille.
Ma question porte sur la manière appropriée de vérifier les messages et d'effectuer un verrouillage autour du code qui accède à la liste. Il n'y a que deux endroits qui accèdent à la liste ; l'un est celui où un message est ajouté à la liste et l'autre est celui où les messages sont transférés de la liste à une liste de traitement.
Ajout d'un message à la liste :
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
Traitement de la liste :
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...
Mes questions sont les suivantes : dois-je faire un double contrôle lorsque je traite la liste, voir ci-dessous ? Et pourquoi ? Ou pourquoi pas ? Et y a-t-il un danger à accéder à la propriété count de la liste en dehors du verrou ? L'une ou l'autre des méthodes est-elle meilleure ou plus efficace ? Et pourquoi ? Ou pourquoi pas ?
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...
Je comprends que dans ce cas particulier, il n'est peut-être pas important que je fasse une double vérification étant donné que, en dehors de la fonction de traitement, la liste ne peut que s'allonger. Mais c'est mon exemple de travail et j'essaie d'apprendre les détails les plus fins du threading.
Merci d'avance pour tout commentaire
Après quelques recherches supplémentaires, merci "le raton laveur", et une programmation expérimentale, j'ai quelques idées supplémentaires.
Concernant le ReaderWriterLockSlim J'ai l'exemple suivant qui semble fonctionner correctement. Il me permet de lire le nombre de messages dans la liste sans interférer avec d'autres codes qui pourraient essayer de lire le nombre de messages dans la liste, ou les messages eux-mêmes. Et lorsque je souhaite traiter la liste, je peux faire passer mon verrou en mode écriture, déverser les messages dans une liste de traitement et les traiter en dehors de tout verrou de lecture/écriture, sans bloquer les autres threads qui pourraient vouloir ajouter ou lire d'autres messages.
Veuillez noter que cet exemple utilise une construction plus simple pour le message, une chaîne, par opposition à l'exemple précédent qui utilisait un type avec d'autres métadonnées.
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
Le seul problème que je vois avec cette technique est que le développeur doit être diligent pour acquérir et libérer les verrous. Sinon, des blocages se produiront.
Quant à savoir si cette méthode est plus efficace que l'utilisation d'un fichier de type SyncLock je ne saurais vraiment pas dire. Pour cet exemple particulier et son utilisation, je pense que l'un ou l'autre suffirait. Je ne ferais pas de double vérification pour les mêmes raisons que celles invoquées par "the coon" concernant la lecture du compte pendant que quelqu'un d'autre le modifie. Dans cet exemple, le SyncLock fournirait la même fonctionnalité. Toutefois, dans un système un peu plus complexe, dans lequel plusieurs sources pourraient lire et écrire dans la liste, l'option ReaderWriterLockSlim serait idéal.
Concernant le BlocageCollection l'exemple suivant fonctionne comme celui qui précède.
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
La simplicité même