2009-04-27 16 views
11

No se trata de los diferentes métodos que podría o debería utilizar para utilizar las colas de la mejor manera, más bien algo que he visto que sucede y que no tiene sentido para mí.C# Enhebrado y colas

void Runner() { 
    // member variable 
    queue = Queue.Synchronized(new Queue()); 
    while (true) { 
     if (0 < queue.Count) { 
      queue.Dequeue(); 
     } 
    } 
} 

Esto se ejecuta en un solo hilo:

var t = new Thread(Runner); 
t.IsBackground = true; 
t.Start(); 

Otros eventos son "Poner en cola" ing en otro lado. Lo que he visto suceder es a través de un período de tiempo, el Dequeue lanzará InvalidOperationException, cola vacía. Esto debería ser imposible ya que la cuenta garantiza que hay algo allí, y estoy seguro de que nada más es "Dequeue" ing.

La pregunta (s):

  1. ¿Es posible que la puesta en cola en realidad aumenta el conteo antes de que el artículo es totalmente en la cola (sea lo que sea ...)?
  2. ¿Es posible que el hilo se esté reiniciando de alguna manera (caducando, reiniciando ...) en la declaración Dequeue, pero inmediatamente después de que ya eliminó un elemento?

Editar (clarificación):

Estas piezas de código son parte de una clase de contenedor que implementa el subproceso de fondo ayudante. El Dequeue aquí es el único Dequeue, y todos Enqueue/Dequeue están en la variable miembro sincronizada (queue).

+0

Debido a la respuesta de Ryan ... ¿es este el código real o simplemente un ejemplo simplificado? Si es el código real, realmente deberías pensar en cambiar el ciclo: consultar la cola en lugar de sincronizar el lector con los escritores es un diseño pobre. Está desperdiciando millones de ciclos de procesador para calentar la habitación. –

+0

Este es un ejemplo para llegar a la carne del problema. Hay un Thread. Sleep allí, el procesador no está siendo martillado. La razón por la que opté por un proceso de votación en lugar de lectura/escritura de sincronización se debe al hecho de que la cola tiene algo casi todo el tiempo. En nuestro baúl, aunque he agregado un AutoResetEvent para jugar. Como dije en la parte superior, no estoy terriblemente preocupado por la implementación aquí. Parece que hay un problema real con este modelo de enhebrado, ya sea correcto o incorrecto. – neouser99

+0

Al mirar tu código, al menos tienes el hilo principal y el hilo donde crees que se llama a Dequeue. Por qué no nombrar sus hilos, y cada vez que se llama a Dequeue, ingrese el nombre del hilo con un seguimiento de la pila. Es posible que encuentre algo en el hilo principal que se comporte de una manera que no esperaba. –

Respuesta

11

Al usar Reflector, puede ver que no, el conteo no aumenta hasta después de que se agrega el elemento.

Como señala Ben, parece que tienes varias personas llamando a dequeue.

Usted dice que está seguro de que nada más está llamando a dequeue. ¿Eso es porque solo tienes el hilo llamado dequeue? ¿Dequeue se llama en cualquier otro lugar?

EDIT:

escribí un pequeño código de ejemplo, pero no pudo conseguir el problema de reproducir. Simplemente siguió funcionando sin excepciones.

¿Cuánto tiempo estuvo funcionando antes de obtener errores? Tal vez puedas compartir un poco más del código.

class Program 
{ 
    static Queue q = Queue.Synchronized(new Queue()); 
    static bool running = true; 

    static void Main() 
    { 
     Thread producer1 = new Thread(() => 
      { 
       while (running) 
       { 
        q.Enqueue(Guid.NewGuid()); 
        Thread.Sleep(100); 
       } 
      }); 

     Thread producer2 = new Thread(() => 
     { 
      while (running) 
      { 
       q.Enqueue(Guid.NewGuid()); 
       Thread.Sleep(25); 
      } 
     }); 

     Thread consumer = new Thread(() => 
      { 
       while (running) 
       { 
        if (q.Count > 0) 
        { 
         Guid g = (Guid)q.Dequeue(); 
         Console.Write(g.ToString() + " "); 
        } 
        else 
        { 
         Console.Write(" . "); 
        } 
        Thread.Sleep(1); 
       } 
      }); 
     consumer.IsBackground = true; 

     consumer.Start(); 
     producer1.Start(); 
     producer2.Start(); 

     Console.ReadLine(); 

     running = false; 
    } 
} 
+0

Muy bien, en realidad tengo un caso de prueba que se ve muy cerca de esto, y se entiende. El servicio se estuvo ejecutando durante más de 2 semanas, creo que antes de que se bloqueara. – neouser99

+8

En este punto, todo lo que puedo sugerir es esto: (1) Ingresó a la sección If, (2) A Solar Flare volteó su bit a cero, (3) Su cola arrojó una excepción. Realmente debería haber sido arrojado una SolarFlareException, pero lo que sea. Potatoh, Potahto. –

3

Esto es lo que creo que la secuencia es problemática:

  1. (0 < queue.Count) como resultado true, la cola no está vacía.
  2. Este hilo obtiene preempted y se ejecuta otro hilo.
  3. El otro hilo elimina un elemento de la cola y lo vacía.
  4. Este subproceso reanuda la ejecución, pero ahora está dentro del bloque if e intenta quitar la cola de una lista vacía.

Sin embargo, usted no dice nada más está desencola ...

Pruebe la salida de la cuenta si en el interior del bloque. Si ve el recuento saltar números hacia abajo, alguien más está dequeuing.

+2

Esta fue mi primera idea también, pero afirmó "Estoy seguro de que nada más es" Dequeue "ing". – BFree

+0

De alguna manera mis pensamientos, pero creo que debe aclarar los hechos que lo hacen tan seguro de que nada más está llamando a Dequeue. –

+0

+1. Hay una condición de carrera entre Count y Dequeue, independientemente de si hay otro hilo * actualmente * dequeuing. Lo mejor es arreglar la condición de carrera y pasar al problema real. –

3

He aquí una posible respuesta de the MSDN page sobre este tema:

enumerar a través de una colección se intrínsecamente no es un procedimiento seguro para subprocesos . Incluso cuando una colección está sincronizada , otros hilos pueden todavía modificar la colección, lo que hace que el enumerador arroje una excepción. Para garantizar la seguridad de subprocesos durante la enumeración , puede bloquear la colección durante toda la enumeración o detectar las excepciones resultantes de los cambios realizados por otros subprocesos .

Supongo que tienes razón: en algún momento, hay una condición de carrera y terminas eliminando algo que no está allí.

A Mutex o Monitor.Lock es probablemente apropiado aquí.

¡Buena suerte!

2

son las otras áreas que son datos "enqueuing" también con el mismo objeto de cola sincronizada? Para que Queue.Synchronized sea seguro para subprocesos, todas las operaciones Enqueue y Dequeue deben usar el mismo objeto de cola sincronizada.

De MSDN:

para garantizar la seguridad hilo de la cola , todas las operaciones se deben realizar a través sólo que esta envoltura.

Editado: Si se recorre durante muchos artículos que implican el cálculo pesado o si está utilizando un lazo de hilo largo plazo (comunicaciones, etc.), se debe considerar que tiene una función de espera como System.Threading.Thread.Sleep, System.Threading.WaitHandle.WaitOne , System.Threading.WaitHandle.WaitAll o System.Threading.WaitHandle.WaitAny en el bucle, de lo contrario, podría matar el rendimiento del sistema.

+0

"[...] asegúrese de tener System.Threading.Thread.Sleep (1) en cualquier bucle de hilo [...]"!?! ¿Qué? ¿Por qué deberías hacer algo como eso? –

+0

Ahhh ... lo tengo. Quieres decir mientras (verdadero) {}. Y no, realmente no deberías agregar Thread.Sleep() a este ciclo. No debe agregar Thread.Sleep() a ningún bucle. Este es solo un diseño muy pobre y debe corregirse: el hilo del lector debe sincronizarse con el hilo del escritor en lugar de sondear la cola. –

+1

Sleep permite que el sistema comparta CPU y recursos; de lo contrario, el hilo consumirá recursos significativos (en algunos casos privará a otros hilos y procesos). Claramente, este es código de muestra, pero hay un tiempo (verdadero) sin condición de salida, así que lo mencioné. Sleep (0) tiene problemas para no ceder el control a los subprocesos de menor prioridad, por lo que se recomienda Sleep (1). Si el ciclo del hilo está temporizado o tiene un lapso de tiempo limitado, entonces puede no ser necesario dormir. He visto una serie de servicios de Windows donde una única declaración de suspensión cambia el uso de la CPU de casi 80-100% a <1%. – Ryan

1

pregunta 1: Si está utilizando una cola sincronizada, entonces: ¡no, está seguro! Pero necesitará usar la instancia sincronizada en ambos lados, el proveedor y el alimentador.

pregunta 2: Terminar el hilo de trabajo cuando no hay trabajo que hacer, es un trabajo simple. Sin embargo, de cualquier manera necesita un hilo de supervisión o hacer que la cola inicie un hilo de trabajo en segundo plano siempre que la cola tenga algo que ver. El último suena más como el Patrón de ActiveObject, que una simple cola (que es Single-Responsibily-Pattern dice que solo debería hacer cola).

Además, me gustaría ir a una cola de bloqueo en lugar de su código anterior. La forma en que funciona su código requiere potencia de procesamiento de la CPU, incluso si no hay trabajo por hacer. Una cola de bloqueo le permite a su trabajador detenerse cuando no haya nada que hacer. Puede tener varios subprocesos en ejecución sin utilizar la potencia de procesamiento de la CPU.

C# no viene con una implementación de cola de bloqueo, pero hay muchos por ahí. Consulte esto example y este one.