Ist in den beiden folgenden Ausschnitten der erste sicher oder müssen Sie den zweiten ausführen?
Mit sicher meine ich, ist garantiert, dass jeder Thread die Methode auf dem Foo aus derselben Schleifeniteration aufruft, in der der Thread erstellt wurde?
Oder müssen Sie den Verweis auf eine neue Variable "local" in jede Iteration der Schleife kopieren?
var threads = new List<Thread>();
foreach (Foo f in ListOfFoo)
{
Thread thread = new Thread(() => f.DoSomething());
threads.Add(thread);
thread.Start();
}
- -
var threads = new List<Thread>();
foreach (Foo f in ListOfFoo)
{
Foo f2 = f;
Thread thread = new Thread(() => f2.DoSomething());
threads.Add(thread);
thread.Start();
}
Update: Wie in Jon Skeets Antwort ausgeführt, hat dies nichts spezielles mit Threading zu tun.
c#
enumeration
closures
xyz
quelle
quelle
Antworten:
Bearbeiten: Dies alles ändert sich in C # 5, mit einer Änderung an der Stelle, an der die Variable definiert ist (in den Augen des Compilers). Ab C # 5 sind sie gleich .
Vor C # 5
Der zweite ist sicher; das erste ist nicht.
Mit
foreach
wird die Variable außerhalb der Schleife deklariert - dhFoo f; while(iterator.MoveNext()) { f = iterator.Current; // do something with f }
Dies bedeutet, dass es nur 1
f
in Bezug auf den Schließungsbereich gibt und die Threads sehr wahrscheinlich verwirrt werden - die Methode wird in einigen Fällen mehrmals aufgerufen und in anderen überhaupt nicht. Sie können dies mit einer zweiten Variablendeklaration innerhalb der Schleife beheben :foreach(Foo f in ...) { Foo tmp = f; // do something with tmp }
Dies hat dann
tmp
in jedem Schließungsbereich einen eigenen , so dass kein Risiko für dieses Problem besteht.Hier ist ein einfacher Beweis für das Problem:
static void Main() { int[] data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; foreach (int i in data) { new Thread(() => Console.WriteLine(i)).Start(); } Console.ReadLine(); }
Ausgaben (zufällig):
1 3 4 4 5 7 7 8 9 9
Fügen Sie eine temporäre Variable hinzu und es funktioniert:
foreach (int i in data) { int j = i; new Thread(() => Console.WriteLine(j)).Start(); }
(jede Nummer einmal, aber natürlich ist die Bestellung nicht garantiert)
quelle
Die Antworten von Pop Catalin und Marc Gravell sind richtig. Ich möchte nur einen Link zu meinem Artikel über Schließungen hinzufügen (der sowohl über Java als auch über C # spricht). Ich dachte nur, es könnte ein bisschen Mehrwert bringen.
EDIT: Ich denke, es lohnt sich, ein Beispiel zu nennen, das nicht die Unvorhersehbarkeit von Threading hat. Hier ist ein kurzes, aber vollständiges Programm, das beide Ansätze zeigt. Die Liste "schlechte Aktion" wird zehnmal zehnmal ausgedruckt. Die Liste "Gute Aktion" zählt von 0 bis 9.
using System; using System.Collections.Generic; class Test { static void Main() { List<Action> badActions = new List<Action>(); List<Action> goodActions = new List<Action>(); for (int i=0; i < 10; i++) { int copy = i; badActions.Add(() => Console.WriteLine(i)); goodActions.Add(() => Console.WriteLine(copy)); } Console.WriteLine("Bad actions:"); foreach (Action action in badActions) { action(); } Console.WriteLine("Good actions:"); foreach (Action action in goodActions) { action(); } } }
quelle
for
ich verstehe, dass die Variable außerhalb der Schleife existiert, ist dieses Verhalten für mich verwirrend. In Ihrem Beispiel für das Schließverhalten , stackoverflow.com/a/428624/20774 , ist die Variable beispielsweise außerhalb des Schließens vorhanden und wird dennoch korrekt gebunden . Warum ist das anders?Wenn Sie Option 2 verwenden müssen, um einen Abschluss um eine sich ändernde Variable zu erstellen, wird der Wert der Variablen verwendet, wenn die Variable verwendet wird, und nicht zum Zeitpunkt der Erstellung des Abschlusses.
Die Implementierung anonymer Methoden in C # und ihre Folgen (Teil 1)
Die Implementierung anonymer Methoden in C # und ihre Folgen (Teil 2)
Die Implementierung anonymer Methoden in C # und ihre Folgen (Teil 3)
Bearbeiten: Um es klar zu machen, sind C # -Verschlüsse " lexikalische Verschlüsse ", dh sie erfassen nicht den Wert einer Variablen, sondern die Variable selbst. Das bedeutet, dass beim Erstellen eines Abschlusses für eine sich ändernde Variable der Abschluss tatsächlich ein Verweis auf die Variable ist und keine Kopie ihres Werts.
Edit2: Links zu allen Blog-Posts hinzugefügt, wenn jemand Interesse daran hat, über Compiler-Interna zu lesen.
quelle
Dies ist eine interessante Frage, und es scheint, als hätten wir gesehen, wie Menschen auf alle möglichen Arten geantwortet haben. Ich hatte den Eindruck, dass der zweite Weg der einzig sichere Weg sein würde. Ich habe einen sehr schnellen Beweis erbracht:
class Foo { private int _id; public Foo(int id) { _id = id; } public void DoSomething() { Console.WriteLine(string.Format("Thread: {0} Id: {1}", Thread.CurrentThread.ManagedThreadId, this._id)); } } class Program { static void Main(string[] args) { var ListOfFoo = new List<Foo>(); ListOfFoo.Add(new Foo(1)); ListOfFoo.Add(new Foo(2)); ListOfFoo.Add(new Foo(3)); ListOfFoo.Add(new Foo(4)); var threads = new List<Thread>(); foreach (Foo f in ListOfFoo) { Thread thread = new Thread(() => f.DoSomething()); threads.Add(thread); thread.Start(); } } }
Wenn Sie dies ausführen, werden Sie sehen, dass Option 1 definitiv nicht sicher ist.
quelle
In Ihrem Fall können Sie das Problem vermeiden, ohne den Kopiertrick zu verwenden, indem Sie
ListOfFoo
es einer Folge von Threads zuordnen:var threads = ListOfFoo.Select(foo => new Thread(() => foo.DoSomething())); foreach (var t in threads) { t.Start(); }
quelle
Beide sind ab C # Version 5 (.NET Framework 4.5) sicher. Weitere Informationen finden Sie in dieser Frage: Wurde die Verwendung von Variablen durch foreach in C # 5 geändert?
quelle
verweist auf die gleiche Referenz wie
Also nichts verloren und nichts gewonnen ...
quelle