2010-02-12 10 views
7

Ich habe ein Beispiel, dass ich jedes Mal brechen kann, wenn ich einen Iterator benutze, aber es funktioniert gut mit einer for-Schleife. Der gesamte Code verwendet für die ausführende Methode lokale Variablen. Ich bin ratlos. Es gibt entweder eine Tatsache über Iteratoren, die mir nicht bekannt sind, oder es gibt einen "ehrlich zu gut" -Bug in .Net. Ich wette auf Ersteres. Bitte helfen.Warum sollte ein Iterator (.Net) in diesem Code unzuverlässig sein?

Dieser Code funktioniert zuverlässig jedes Mal. Es durchläuft alle Elemente nacheinander (z. B. 10) und startet einen neuen Thread, wobei die Ganzzahl als Argument in einer Methode an den neuen Thread übergeben wird. Es startet 10 Fäden, einen für jeden Gegenstand. 1,2,3,4,5,6,7,8,9,10 - das funktioniert immer.

ARBEITSCode:

//lstDMSID is a populated List<int> with 10 elements. 
for(int i=0; i<lstDMSID.Count; i++) 
{ 
    int dmsId = lstDMSID[i]; 
    ThreadStart ts = delegate 
    { 
     // Perform some isolated work with the integer 
     DoThreadWork(dmsId); 
    }; 
    Thread thr = new Thread(ts); 
    thr.Name = dmsId.ToString(); 
    thr.Start(); 
} 

Und dieser Code tatsächlich Elemente wiederholen. Es durchläuft alle Elemente nacheinander (z. B. 10) und startet einen neuen Thread. Es startet 10 Threads, aber nicht alle 10 ganzen Zahlen. Ich sehe es beginnen 1,2,3,3,6,7,7,8,9,10. Ich verliere Zahlen.

BUSTED CODE:

//lstDMSID is a populated List<int> with 10 elements. 
foreach(int dmsId in lstDMSID) 
{ 
    ThreadStart ts = delegate 
    { 
     // Perform some isolated work with the integer 
     DoThreadWork(dmsId); 
    }; 
    Thread thr = new Thread(ts); 
    thr.Name = dmsId.ToString(); 
    thr.Start(); 
} 
+0

als Notiz, sind die wiederholten ganzen Zahlen nicht immer die gleich. es scheint zufällig zu sein. –

Antwort

12

Das Problem beruht auf dem Verschluss für Ihre generierbaren ...

Das gleiche Problem in der for-Schleife passieren würde, waren Sie es, wie so neu zu schreiben (BAD CODE!):

// ...Closure now happens at this scope... 
for(int i=0;i<lstDMSID.Count;i++) 
{ 
    ThreadStart ts = delegate 
    { 
     DoThreadWork(lstDMSID[i]); // Eliminate the temporary, and it breaks! 
    }; 
    Thread thr = new Thread(ts); 
    thr.Name = dmsId.ToString(); 
    thr.Start(); 
} 

Das Problem ist, wenn Sie über eine Variable in einem Delegaten schließen (in Ihrem Fall dmsId), die Schließung geschieht in dem Bereich, in dem die Variable deklariert wird. Wenn Sie eine For- oder Foreach-Schleife verwenden, erfolgt die Schließung im Rahmen der For/Foreach-Anweisung, die eine Ebene zu hoch ist.

eine temporäre Variable innerhalb der foreach-Schleife Einführung in das Problem beheben:

foreach(int dmsId in lstDMSID) 
{ 
    int temp = dmsId; // Add temporary 
    ThreadStart ts = delegate 
    { 
     DoThreadWork(temp); // close over temporary, and it's fixed 
    }; 
    Thread thr = new Thread(ts); 
    thr.Name = dmsId.ToString(); 
    thr.Start(); 
} 

Für eine ausführlichere Diskussion darüber, was passiert ist, würde ich empfehlen Eric Lippert's blog post: "Closing over the loop variable considered harmful" lesen.

+0

@Reed Copsey - große Erklärung und großer Referenzartikel. Aufgrund der Anzahl der Antworten hätte ich das wahrscheinlich wissen müssen. Ich weiß es jetzt! –

+0

@Jride: Es ist etwas, was viele Entwickler vermissen, da es kein Problem zu sein scheint, bis Sie Threading implementieren (die meiste Zeit). Ich erwähne das immer in meinen Parallelgesprächen, genau aus diesem Grund. –

1

Im ersten Fall, dmsId innerhalb des Schutzbereichs der for-Schleife deklariert wird, einfängt Jeder Teilnehmer seine eigene „instance“ dieser Variablen.

In der zweiten Version wird dmsId für den gesamten Bereich der foreach-Schleife deklariert. Jeder Delegat erfasst die gleiche Variable - was bedeutet, dass Sie von mehreren Threads auf die gleiche Variable zugreifen, ohne dass sie gesperrt werden - es kann zu schlechtem Zeug kommen.

3

Dies liegt am Umfang der Variablen, die Sie im Abschluss verwenden.

Eric Lippert hat eine nice blog post explaining this im Detail, und ich denke, dass andere (Jon Skeet?) Haben darüber auch gebloggt.

3

Wenn Sie eine anonyme Methode in Ihrem foreach ausführen, generiert der Compiler im Grunde eine Klasse, die auf DmsId verweist. Wenn also Threads gestartet werden, zeigt jeder auf die gleiche Variable. Je nachdem, wann die Threads geplant sind, werden die Zahlen doppelt oder übersprungen angezeigt.

In der for-Schleife erstellen Sie eine Kopie der ganzen Zahl, so dass jeder Thread seinen eigenen Wert erhält.

Es gibt einige gute Daten zu diesem Problem here.

2

Das Problem ist, dass Verschlüsse über Variablen, nicht über Werte schließen. Das bedeutet, dass alle Teilnehmer einen Verweis auf die gleiche Variable erhalten und den Wert der Variablen ändert sich jedes Mal durch die Schleife

Dies sollte es beheben:

//lstDMSID is a populated List with 10 elements. 
foreach(int dmsId in lstDMSID) 
{ 
    int tempId = dmsId; 
    ThreadStart ts = delegate 
    { 
     //this is method that goes off ad does some isolated work with the integer 
     DoThreadWork(tempId); 
    }; 
    Thread thr = new Thread(ts); 
    thr.Name = tempId.ToString(); 
    thr.Start(); 
} 
+0

Das wird immer noch brechen - es ist im Grunde identisch mit dem Problem. Die eingeführte Variable muss innerhalb der foreach-Schleife liegen. Dann wird der Verschluss richtig erzeugt. –

+0

Besser;) Das hat es behoben. –

+0

Nein, ich habe versehentlich auf submit geklickt, bevor ich die Antwort fertig geschrieben habe. Es funktioniert wie gezeigt. – Gabe

Verwandte Themen