2012-02-23 24 views
21

Tengo una lista de objetos y me gustaría pasar por encima de esa lista y comenzar un nuevo hilo, pasando el objeto actual.Inicio de un nuevo hilo en un bucle foreach

He escrito un ejemplo de lo que pensé que debería hacer esto, pero no está funcionando. Específicamente, parece que los hilos se sobrescriben en cada iteración. Sin embargo, esto realmente no tiene sentido para mí porque estoy creando un nuevo objeto Thread cada vez.

Este es el código de prueba que escribí

class Program 
{ 
    static void Main(string[] args) 
    { 
     TestClass t = new TestClass(); 
     t.ThreadingMethod(); 
    } 
} 

class TestClass 
{ 
    public void ThreadingMethod() 
    { 
     var myList = new List<MyClass> { new MyClass("test1"), new MyClass("test2") }; 

     foreach(MyClass myObj in myList) 
     { 
      Thread myThread = new Thread(() => this.MyMethod(myObj)); 
      myThread.Start(); 
     } 
    } 

    public void MyMethod(MyClass myObj) { Console.WriteLine(myObj.prop1); } 
} 

class MyClass 
{ 
    public string prop1 { get; set; } 

    public MyClass(string input) { this.prop1 = input; } 
} 

La salida en mi máquina es

test2 
test2 

pero yo esperaba que fuera

test1 
test2 

He intentado cambiar las líneas de hilo a

ThreadPool.QueueUserWorkItem(x => this.MyMethod(myObj)); 

pero ninguno de los hilos se inició.

Creo que tengo un malentendido acerca de cómo se supone que los hilos funcionan. ¿Puede alguien señalarme en la dirección correcta y decirme qué estoy haciendo mal?

+1

Su vida será mucho más fácil si consulta la Biblioteca de Extensiones Paralelas presentada en .Net 3.5. Aquí hay un lugar para comenzar: http://msdn.microsoft.com/en-us/library/dd460693%28VS.100%29.aspx – DOK

+0

http://www.albahari.com/threading/ –

Respuesta

38

Esto se debe a que está cerrando una variable en el ámbito incorrecto. La solución a este problema es utilizar un temporal en el bucle foreach:

foreach(MyClass myObj in myList) 
    { 
     MyClass tmp = myObj; // Make temporary 
     Thread myThread = new Thread(() => this.MyMethod(tmp)); 
     myThread.Start(); 
    } 

Para más detalles, recomiendo leer el post de Eric Lippert sobre este tema exacto: Closing over the loop variable considered harmful

+1

Me lo ganaste ! –

+0

Esa fue una respuesta rápida. Guau. – BlueM

+1

Gah, me estoy pateando porque realmente leí [esta pregunta] (http://stackoverflow.com/q/8898925/817630) el mes pasado, que es la respuesta. Incluso eché un vistazo a la publicación de blog de Eric.Gracias por señalar lo que debería haberme recordado. –

3

El problema es que está utilizando el valor más actual del objeto dentro de su cierre. Entonces, cada invocación del hilo está buscando el mismo valor. Para evitar esto, copie el valor en una variable local:

foreach(MyClass myObj in myList) 
{ 
    MyClass localCopy = myObj; 
    Thread myThread = new Thread(() => this.MyMethod(localCopy)); 
    myThread.Start(); 
} 
2

De acuerdo con la respuesta de Reed (+1).

Yo agregaría que si está en .NET 4, es posible que desee consultar la Biblioteca de tareas paralelas para resolver esta clase de problema. Específicamente para este caso, eche un vistazo a Parallel.ForEach().

+1

Aunque me gusta Parallel.ForEach, me doy cuenta de que, en sí mismo, es un método de bloqueo, donde los OP son "disparar y olvidar", por lo que hay una diferencia funcional al usarlo. –

1

si la secuencia no es materia de ir a

Parallel.ForEach(myList, obj => this.MyMethod(obj)); 

Write a Simple Parallel.ForEach Loop

+0

Parallel.ForEach en sí mismo, es un método de bloqueo, donde el OP es "disparar y olvidar", por lo que hay una diferencia funcional al usarlo. Esto puede no ser apropiado en este caso (al menos no sin ponerlo dentro de una Tarea en sí) –

1

prefiero de esta manera:

public void ThreadingMethod() 
{ 
    var myList = new List<MyClass> { new MyClass("test1"), new MyClass("test2") }; 


Parallel.ForEach(myList, new ParallelOptions() { MaxDegreeOfParallelism = 100 }, 
     (myObj, i, j) => 
     { 
      MyMethod(myObj); 
     }); 

} 
no

probado aunque ....

+0

Paralelo.ForEach tiene un comportamiento funcional diferente al del método OP. El código original tiene el comportamiento "disparar y olvidar", mientras que esto bloqueará el hilo de llamada hasta que las operaciones se completen. Además, no recomendaría establecer MaxDegreeOfParallelism a menos que haya una razón específica para hacerlo. –

+0

@ReedCopsey - Supongo que solo lo estás asumiendo. el uso de Parallel.ForEach es siempre un método preferido para implementar un subproceso ADECUADO en la aplicación, especialmente si tiene muchos elementos que desea procesar (al mismo tiempo) y no tiene suficientes recursos para utilizar Thread() simple. su implementación es muy pobre si usa 1000 hilos con su método sugerido. – SolidSnake

+0

Parallel.ForEach no siempre es un método preferido. Es una buena opción, pero una de muchas. El uso de Tareas o el ThreadPool suele ser una mejor opción que administrar el hilo por sí mismo, pero aun así, hay ocasiones en las que se prefiere usar el hilo directamente. No hay suficiente información en el código del OP para sugerir que Parallel.ForEach sea una opción preferida. (Por supuesto, casi siempre elegiría una Tarea de ejecución larga sobre un Tema gestionado manualmente, sin embargo) –