2012-07-03 6 views
5

Tengo un servicio en la aplicación que me permite alimentarlo con mensajes de varias fuentes, que se incluirán en una lista simple. El servicio, ejecutándose en su propio hilo, periódicamente procesará todos los mensajes de la lista en varios archivos; un archivo para cada fuente, que luego se gestionan por tamaño.¿Debo verificar dos veces antes y después de bloquear una lista?

Mi pregunta es sobre la forma correcta de buscar mensajes y realizar un bloqueo alrededor del código que accede a la lista. Solo hay dos lugares que acceden a la lista; uno es donde se agrega un mensaje a la lista y el otro es donde los mensajes se vuelcan de la lista a una lista de procesamiento.

Adición de un mensaje a la lista:

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 

Procesamiento de la lista:

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... 

Mis preguntas son: debo hacer un doble control cuando estoy procesando la lista, véase más adelante? ¿Y por qué? O por qué no? ¿Y hay algún peligro al acceder a la propiedad de recuento de la lista fuera del bloqueo? ¿Alguno de los métodos es mejor o más eficiente? ¿Y por qué? O por qué no?

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... 

entiendo que en este caso particular, puede que no importe si hago un doble control dado el hecho de que, fuera de la función de procesamiento, la lista sólo puede crecer. Pero este es mi ejemplo de trabajo y estoy tratando de aprender sobre los detalles más finos de enhebrar.

Gracias de antemano por cualquier idea ...


Después de una investigación más profunda, gracias 'El Coon', y una programación experimental, tengo algunas ideas adicionales.

En cuanto al ReaderWriterLockSlim, tengo el siguiente ejemplo que parece funcionar bien. Me permite leer la cantidad de mensajes en la lista sin interferir con otros códigos que pueden estar tratando de leer la cantidad de mensajes en la lista, o los mensajes mismos. Y cuando deseo procesar la lista, puedo actualizar mi bloqueo al modo de escritura, volcar los mensajes en una lista de procesamiento y procesarlos fuera de cualquier bloqueo de lectura/escritura, sin bloquear así ningún otro hilo que pueda querer agregar o leer , más mensajes.

Tenga en cuenta que este ejemplo utiliza una construcción más simple para el mensaje, una Cadena, a diferencia del ejemplo anterior que utilizó un Tipo junto con algunos otros metadatos.

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 

El único problema que veo con esta técnica es que el desarrollador debe ser diligente en la adquisición y liberación de los bloqueos. De lo contrario, se producirán bloqueos.

En cuanto a si esto es más eficiente que usar un SyncLock, realmente no podría decirlo. Para este ejemplo en particular y su uso, creo que cualquiera sería suficiente. No haría el doble control por las mismas razones que 'el mapache' dio sobre leer el conteo mientras alguien más lo está cambiando. Dado este ejemplo, el SyncLock proporcionaría la misma funcionalidad. Sin embargo, en un sistema un poco más complejo, en el que múltiples fuentes podrían leer y escribir en la lista, el ReaderWriterLockSlim sería ideal.


cuanto a la lista BlockingCollection, el siguiente ejemplo funciona como la de arriba.

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 

simplicidad en sí ...

+0

¿Cuánto control tiene sobre la arquitectura del servicio? Esto parece que una gran cantidad de trabajo pesado se puede hacer mediante el uso de MSMQ. http://msdn.microsoft.com/en-us/library/ms789048.aspx – Trevor

+0

Permítanme aclarar, cuando digo servicio, me refiero a una clase, interna a la aplicación, que se inicia al principio de la aplicación, crea un hilo para procesar periódicamente los mensajes que le dan otras partes de la aplicación, no un servicio de Windows. Un manejador de mensajes generalizado, si lo desea, encapsulado totalmente dentro de la aplicación, ejecutándose en su propio hilo de fondo. Este 'servicio' tiene la tarea de 1) proporcionar una forma simple de crear mensajes, y 2) evitar que el almacenamiento de los mensajes consuma demasiado espacio de almacenamiento. Además, no hay necesidad de mensajería entre aplicaciones. –

+0

Mensaje puede ser la terminología incorrecta para esta aplicación. No es tanto un mensaje como un evento registrado. Un evento de registro de 'depuración', 'mensaje', 'advertencia' o 'error' que es específico y relevante únicamente para la aplicación o módulo que creó el mensaje. –

Respuesta

3

Teoría:

Una vez que un hilo adquiere la _SyncLockObject bloquear todas las demás hilos reingresar ese método tendrá que esperar a que el bloqueo sea liberado.

Por lo tanto, la verificación antes y después del bloqueo es irrelevante. En otras palabras, no tendrá ningún efecto. También es no seguro, porque no está utilizando una lista concurrente.

Si un hilo pasa a marcar Count en la primera prueba mientras que otro está borrando o agregando a la colección, entonces obtendrá una excepción con Collection was modified; enumeration operation may not execute.. Además, la segunda verificación solo se puede ejecutar por un hilo a la vez (ya que está sincronizado).

Esto también se aplica a su método Add. Si bien el bloqueo es propiedad de un hilo (lo que significa que el flujo de ejecución ha llegado a esa línea), ningún otro hilo podrá procesar o agregar a la lista.

También debe tener cuidado de bloquear si solo está leyendo de la lista en otros lugares de su aplicación. Para escenarios de lectura/escritura más complejos (como una colección concurrente personalizada), recomiendo usar ReaderWriterLockSlim.

Práctica:

Utilice un BlockingCollection, ya que es hilo de seguridad (es decir, que maneja concurrencia internamente).

Cuestiones relacionadas