2008-10-30 26 views
12

que tienen una sencilla aplicación con el siguiente código:C# 2.0 Threading de interrogación (métodos anónimos)

FileInfo[] files = (new DirectoryInfo(initialDirectory)).GetFiles(); 
    List<Thread> threads = new List<Thread>(files.Length); 

    foreach (FileInfo f in files) 
    { 
     Thread t = new Thread(delegate() 
     { 
      Console.WriteLine(f.FullName); 
     }); 
     threads.Add(t); 
    } 

    foreach (Thread t in threads) 
     t.Start(); 

Digamos que en 'I = InitialDirectory' directorio tengo 3 archivos. Esta aplicación debe crear 3 hilos, con cada hilo imprimiendo uno de los nombres de archivo; sin embargo, en su lugar, cada hilo imprimirá el nombre del último archivo en la matriz de 'archivos'.

¿Por qué es esto? ¿Por qué la variable 'f' del archivo actual no se está configurando correctamente en el método anónimo?

Respuesta

11

El método anónimo guarda referencia en la variable del bloque adjunto, no el valor real de la variable.

En el momento en que se ejecutan los métodos (cuando inicia los hilos) f ha sido asignado para señalar el último valor en la colección, por lo que los 3 hilos imprimen ese último valor.

+1

Nota para futuros lectores: este comportamiento [realmente cambiará] (http://stackoverflow.com/a/8899347/137188) en C# 5.0. Cada iteración creará una nueva variable de bucle separada. Con ese cambio, el código en esta pregunta se comportaría como el asker originalmente esperado. – tcovo

0

Es porque f.FullName es una referencia a una variable, y no a un valor (que es cómo intentó usarlo). En el momento en que realmente inicia los hilos f.FullName se incrementó hasta llegar al final de la matriz.

De todos modos, ¿por qué repetir las cosas aquí dos veces?

foreach (FileInfo f in files) 
{ 
    Thread t = new Thread(delegate() 
    { 
     Console.WriteLine(f.FullName); 
    }); 
    threads.Add(t); 
    t.Start(); 
} 

Sin embargo, esto sigue siendo incorrecto, y tal vez incluso peor ya que ahora tiene una condición de carrera para ver cuál de ellos va más rápido: escribir el ítem de la consola o la iteración a la siguiente FileInfo.

+0

En este código, los hilos acceden a la variable f antes de que el hilo principal haya terminado de modificarla. –

6

Éstos son algunos artículos agradables sobre los métodos anónimos en C# y el código que será generado por el compilador:

http://blogs.msdn.com/oldnewthing/archive/2006/08/02/686456.aspx
http://blogs.msdn.com/oldnewthing/archive/2006/08/03/687529.aspx
http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx

Creo que si lo hizo:

 
    foreach (FileInfo f in files) 
    { 
     FileInfo f2 = f; //variable declared inside the loop 
     Thread t = new Thread(delegate() 
     { 
      Console.WriteLine(f2.FullName); 
     }); 
     threads.Add(t); 
    } 

funcionaría como usted lo deseaba.

+0

sí, lo haría.Deja de modificar esta respuesta. – Jimmy

+0

Sí, ¡por supuesto que funciona! ¡Gracias! Originalmente estaba pensando que el ciclo foreach estaba haciendo eso automáticamente (nueva variable 'f' por iteración), pero supongo que realmente no tiene ningún sentido que funcione así – John

0

Se debe a que el código subyacente para el iterador (foreach) ya se 'iteró' a través de todos los valores en la Lista antes de que comience el subproceso ... Entonces, cuando comienzan, el valor 'señalado' por el iterador es el último uno en la lista ...

arrancar el flujo dentro de la iteración en lugar ....

foreach (FileInfo f in files) 
{ 
    string filName = f.FullName; 
    Thread t = new Thread(delegate() 
       { Console.WriteLine(filName); }); 
    t.Start(); 
} 

no creo que una carrera es posible aquí ya que no hay memoria compartida accesible desde todas las discusiones.

+0

hay una condición de carrera, f es compartida . Puede probar haciendo que cada hilo duerma durante unos pocos milisegundos: Subproceso t = nuevo Subproceso (delegar() { Thread.Sleep (300); Console.WriteLine (f.FullName); }); –

+0

f es compartida, pero string filName no es ... es una variable de marco de pila y será específica para cada hilo ... –

+0

para que sea una carrera debe haber una variable de memoria compartida cuyo valor sea mutable y erróneo durante alguna parte del proceso ... cadena nombre de archivo se inicializa desde f.NullNam antes de que se creen los subprocesos, y su valor se almacena en el marco de la pila específico de subprocesos –

0

Lo siguiente también podría funcionar.

Thread t = new Thread(delegate() 
    { 
     string name = f.Name; 
     Console.WriteLine(name); 
    }); 
+0

No creo que esto funcione. f todavía se declara fuera del ciclo y se accede dentro de un delegado anónimo –

Cuestiones relacionadas