2011-06-18 11 views
16

Tengo un error extraño en mi código. Es extremadamente raro (sucede una vez cada pocas semanas tal vez), pero está allí y no estoy seguro de por qué.Problemas de cola .NET multiproceso

Tenemos 2 hilos de ejecución, 1 hilo recibe mensajes de red y los añade a una cola de esta manera:

DataMessages.Enqueue(new DataMessage(client, msg)); 

Otro hilo lleva mensajes de esta cola y las manijas, así:

while (NetworkingClient.DataMessages.Count > 0) 
{ 
    DataMessage message = NetworkingClient.DataMessages.Dequeue(); 

    switch (message.messageType) 
    { 
     ... 
    } 
} 

Sin embargo, de vez en cuando obtengo una NullReferenceException en la línea switch (message.messageType) y puedo ver en el depurador que el mensaje es nulo.

No es posible que se haya puesto un valor nulo en la cola (vea el primer bit del código), y estas son las únicas 2 cosas que usan la cola.

¿Queue no es seguro para subprocesos, podría ser que estoy arrastrando en el momento exacto en que el otro subproceso está en cola y esto causa la falla?

+0

muy pocas cosas en .NET BCL son seguras para hilos de esta manera. En este caso, "para garantizar la seguridad del subproceso de la Cola, todas las operaciones se deben realizar a través del contenedor devuelto por [Sincronizado] (http://msdn.microsoft.com/en-us/library/system.collections.queue .aspx) método ". Su problema también podría ser que dos hilos estén en secuencia o dequeue simultáneamente, si corresponde. De cualquier manera, la seguridad de la rosca es su responsabilidad. – bzlm

+0

+1, Buena pregunta, realmente demuestra una condición de carrera y su resultado (situación corrupta/inestable/inesperada). –

+0

Por la apariencia de tu código, parece que estás haciendo un ciclo ingenuo para tus hilos. Debería considerar la implementación de un [buffer-buffer] adecuado (http://en.wikipedia.org/wiki/Producer-consumer_problem) para sincronizar esto. –

Respuesta

9
while (NetworkingClient.DataMessages.Count > 0) 
    { 
     // once every two weeks a context switch happens to be here. 
     DataMessage message = NetworkingClient.DataMessages.Dequeue(); 

     switch (message.messageType) 
     { 
      ... 
     } 
    } 

... y cuando llegue ese cambio de contexto en ese lugar, el resultado de la primera expresión (NetworkingClient.DataMessages.Count > 0) es cierto para ambos hilos, y el que obtener de la operación Dequeue() primero obtener del objeto y el segundo thread get es nulo (en lugar de InvalidOperationException porque el estado interno de la cola no se actualizó completamente para arrojar la excepción correcta).

Ahora usted tiene dos opciones:

  1. utilizar .NET 4,0 ConcurrentQueue

  2. refactorizar su código:

y hacer que se vea de alguna manera como esto:

while(true) 
{ 
    DataMessage message = null; 

    lock(NetworkingClient.DataMessages.SyncRoot) { 
     if(NetworkingClient.DataMessages.Count > 0) { 
      message = NetworkingClient.DataMessages.Dequeue(); 
     } else { 
     break; 
     } 
    } 
    // .. rest of your code 
} 

Editar: actualizado para reflejar el comentario de Heandel.

+2

Puede usar el objeto 'SyncRoot' de' Queue' para '_sync'. ¡Ese es su propósito! –

+0

Tienes toda la razón. ¡Gracias! –

+0

la pregunta dice "Otro hilo elimina mensajes de esta cola y los maneja". Esto significaría que una secuencia simultánea de 2 hilos nunca ocurriría. #justsaying – bzlm

11

Está cola no apta para subprocesos, no podía ser de que estoy desencola en el momento exacto que el otro hilo se enqueuing y esto hace que el fallo?

Exactamente. Queue no es seguro para subprocesos. Una cola segura para hilos es System.Collections.Concurrent.ConcurrentQueue. Úselo en su lugar para arreglar su problema.

+0

Ni siquiera sabía sobre este ConcurrentQueue - siempre se usa el candado ... – VikciaR

+0

Es nuevo en .NET 4;) –

+2

No use 'ConcurrentQueue' a ciegas; solo úsala si sabes lo que estás haciendo. En algunas situaciones, 'Queue' con bloqueos es una mejor opción. Las clases de colección simultáneas no son remedios milagrosos para problemas de concurrencia :) – Timwi

7

En caso de estar interesado en la razón exacta:

Enqueue se parece a esto:

this._array[this._tail] = item; 
this._tail = (this._tail + 1) % this._array.Length; 
this._size++; 
this._version++; 

Y Dequeue así:

T result = this._array[this._head]; 
this._array[this._head] = default(T); 
this._head = (this._head + 1) % this._array.Length; 
this._size--; 
this._version++; 

La carrera es la siguiente:

  • Hay 1 elemento en una cola (cabeza == cola) para que su hilo lector comienza desencola pero es interrumpido después de la primera línea en Dequeue
  • Entonces se encola otro elemento y se puso en la posición tail que es igual a head en este punto .
  • ahora Dequeue hojas de vida y sobrescribe el elemento que acaba de ser insertado por Enqueue con default(T)
  • La próxima vez que llame a quitar de la cola se obtiene el valor predeterminado (T) (en su caso nulo) en lugar del valor real
+1

+1, es agradable ver exactamente lo que está sucediendo detrás de escena. ¡Creo que debería tener más cuidado cuando multiplique! Me alegro de haber encontrado este error y no lo incluiré en el lanzamiento. – Hannesh

Cuestiones relacionadas