2011-01-20 27 views
6

Por favor, compruebe el código de ejemplo a continuación:comportamiento inesperado para ThreadPool.QueueUserWorkItem

public class Sample 
{ 
    public int counter { get; set; } 
    public string ID; 
    public void RunCount() 
    { 
     for (int i = 0; i < counter; i++) 
     { 
      Thread.Sleep(1000); 

      Console.WriteLine(this.ID + " : " + i.ToString()); 
     } 
    } 
} 

class Test 
{ 
    static void Main() 
    { 
     Sample[] arrSample = new Sample[4]; 

     for (int i = 0; i < arrSample.Length; i++) 
     { 
      arrSample[i] = new Sample(); 
      arrSample[i].ID = "Sample-" + i.ToString(); 
      arrSample[i].counter = 10; 
     } 

     foreach (Sample s in arrSample) 
     { 
      ThreadPool.QueueUserWorkItem(callback => s.RunCount()); 
     } 

     Console.ReadKey(); 
    } 

} 

La salida esperada para esta muestra debe ser algo como:

Sample-0 : 0 
Sample-1 : 0 
Sample-2 : 0 
Sample-3 : 0 
Sample-0 : 1 
Sample-1 : 1 
Sample-2 : 1 
Sample-3 : 1 
. 
. 
. 

Sin embargo, cuando se ejecuta este código, mostraría algo como esto en su lugar:

Sample-3 : 0 
Sample-3 : 0 
Sample-3 : 0 
Sample-3 : 1 
Sample-3 : 1 
Sample-3 : 0 
Sample-3 : 2 
Sample-3 : 2 
Sample-3 : 1 
Sample-3 : 1 
. 
. 
. 

Puedo entender que el orden en el que el trío Los anuncios que se ejecutan pueden diferir y, por lo tanto, el recuento no aumenta en la modalidad de round robin. Sin embargo, no entiendo por qué todos los ID s se muestran como Sample-3, mientras que la ejecución está sucediendo claramente independientemente uno del otro.

¿Se están utilizando diferentes objetos con diferentes subprocesos?

+0

Otro día, otra persona sin saber cómo utilizar adecuadamente los cierres. No es de extrañar que Java sea tan reacia a agregarlos ... – leppie

+1

Lo que leppie está diciendo es que captura la variable 's' en lugar de su valor. Para cuando los hilos se ejecutan, la iteración termina y 's' queda con su último valor. – Zarat

+0

Creo que estoy perdido aquí .. ¿Qué es el problema de cierre BTW –

Respuesta

10

Este es el viejo problema de cierre modificado. Es posible que desee consultar: Threadpools - possible thread execution order problem para una pregunta similar, y la publicación de blog de Eric Lippert Closing over the loop variable considered harmful para comprender el problema.

En esencia, la expresión lambda que tienes ahí es la captura de la variable de s en lugar del valor de la variable en el punto es declarado lambda. En consecuencia, los cambios posteriores realizados en el valor de la variable son visibles para el delegado. La instancia de Sample en la que se ejecutará el método RunCount dependerá de la instancia a la que hace referencia la variable s (su valor) en el punto en que el delegado realmente ejecuta.

Además, dado que los delegados (el compilador realmente reutiliza la misma instancia delegada) se están ejecutando de forma asíncrona, no se garantiza qué valores tendrán en el punto de cada ejecución. Lo que está viendo actualmente es que el bucle foreach completa en el subproceso principal antes de cualquiera de las invocaciones de delegado (como es de esperar, lleva tiempo programar tareas en el grupo de subprocesos). Entonces todos los elementos de trabajo terminan viendo el valor 'final' de la variable de bucle. Pero esto no está garantizado de ninguna manera; intente insertar un Thread.Sleep de duración razonable dentro del bucle, y verá una salida diferente.


La solución habitual es:

  1. introducir otro dentro el bucle de cuerpo variable.
  2. Asigna esa variable al valor actual de la variable de bucle.
  3. Captura la variable 'copiar' en lugar de la variable de bucle dentro de la lambda.

    foreach (Sample s in arrSample) 
    { 
        Sample sCopy = s; 
        ThreadPool.QueueUserWorkItem(callback => sCopy.RunCount()); 
    } 
    

Ahora cada obra-elemento "posee" un valor particular de la variable de bucle.


Otra opción en este caso es esquivar el problema por completo al no capturar cualquier cosa:

ThreadPool.QueueUserWorkItem(obj => ((Sample)obj).RunCount(), s); 
+1

¡Arghhh, maldita mujer llamada! Pero las grandes mentes piensan igual :) – leppie

+0

gracias @ani, eso tiene sentido. ¡No me extraña que me quemaran! Debería haber sabido esto .. :) –

Cuestiones relacionadas