2008-10-30 19 views
12

Ich habe eine einfache Anwendung mit dem folgenden Code:C# 2.0 Threading Frage (anonyme Methoden)

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(); 

Können sagen, in 'I = Initial' Verzeichnis Ich habe 3 Dateien. Diese Anwendung sollte dann 3 Threads erstellen, wobei jeder Thread einen der Dateinamen ausdruckt. Stattdessen wird jeder Thread den Namen der letzten Datei im Array 'files' ausgeben.

Warum ist das? Warum wird die aktuelle Datei 'f' Variable nicht richtig in der anonymen Methode eingerichtet?

Antwort

11

Die anonyme Methode behält eine Referenz auf die Variable im umschließenden Block - nicht den tatsächlichen Wert der Variablen.

Zu dem Zeitpunkt, zu dem die Methoden tatsächlich ausgeführt werden (wenn Sie die Threads starten) wurde f zugewiesen, um auf den letzten Wert in der Auflistung zu zeigen, so dass alle 3 Threads den letzten Wert drucken.

+1

Hinweis für zukünftige Leser: Dieses Verhalten [wird sich tatsächlich ändern] (http://stackoverflow.com/a/8899347/137188) in C# 5.0. Jede Iteration erstellt eine neue separate Schleifenvariable. Mit dieser Änderung würde sich der Code in dieser Frage so verhalten, wie der ursprünglich erwartete Asker. – tcovo

0

Es ist, weil f.FullName ist eine Referenz auf eine Variable, und kein Wert (wie Sie es versucht haben, zu verwenden). Zu dem Zeitpunkt, zu dem Sie tatsächlich die Threads starten, wurde f.FullName bis zum Ende des Arrays inkrementiert.

Wie auch immer, warum durchlaufen Sie die Dinge hier zweimal?

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

Dies ist jedoch immer noch falsch, und vielleicht noch schlimmer, da Sie jetzt eine Race-Bedingung zu sehen haben, welcher Thread geht schneller: die Konsole Artikel zu schreiben oder auf die nächste Fileinfo laufen.

+0

In diesem Code greifen die Threads auf die f-Variable zu, bevor der Haupt-Thread die Änderung abgeschlossen hat. –

6

Hier sind ein paar nette Artikel über anonyme Methoden in C# und dem Code, der vom Compiler generiert wird:

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

Ich denke, wenn du getan hast:

 
    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); 
    } 

es würde funktionieren, wie Sie es wollten.

+0

ja würde es.Hör auf, diese Antwort zu modifizieren. – Jimmy

+0

Ja, das funktioniert wirklich! Vielen Dank! ich ursprünglich dachte, die foreach-Schleife, die automatisch tun wurde (neu ‚f‘ Variable pro Iteration), aber ich denke, dass nicht wirklich keinen Sinn macht es, wie die – John

0

Dies liegt daran, dass der zugrunde liegende Code für Iterator (foreach) bereits durch alle Werte in der Liste "iteriert" hat, bevor die Threads beginnen ... Wenn sie gestartet werden, ist der vom Iterator "angedeutete" Wert der letzte in der Liste ...

den Faden innerhalb der Iteration stattdessen starten ....

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

ich glaube nicht, ein Rennen hier möglich ist, da von allen Threads gibt es keinen gemeinsamen Speicher zugegriffen werden.

+0

zu arbeiten gibt es eine Race-Bedingung ist, f geteilt . Sie können es testen, indem Sie jeden Thread für einige Millisekunden inaktivieren: Thread t = neuer Thread (Delegat() { Thread.Sleep (300); Console.WriteLine (f.FullName); }); –

+0

f gemeinsam genutzt wird, aber ist nicht String filName ... es ist eine variable Stapelrahmen und wird an jeden Thread spezifisch sein ... –

+0

für es ein Rennen sein eine Shared-Memory-Variable sein muss, dessen Wert veränderlich, und falsch Während eines Teils der Verarbeitung ... String-Dateiname wird von f.FullNam initialisiert, bevor die Threads erstellt werden, und es ist Wert auf der Thread-spezifischen Stack-Frame gespeichert –

0

Folgendes würde auch funktionieren.

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

Ich glaube nicht, dass dies funktioniert. f wird immer noch außerhalb der Schleife deklariert und auf den anonymen Delegaten zugegriffen –