Frage

In den beiden folgenden Schnipsel, ist die erste sichere oder müssen Sie das zweite tun?

Durch die sichere ich meine, ist jeder Thread garantiert das Verfahren auf dem Foo aus der gleichen Schleifeniterationslatenzzeit nennen, in dem der Thread erstellt wurde?

Oder müssen Sie den Verweis auf eine neue Variable kopieren „local“ zu jeder Iteration der Schleife?

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 Skeet Antwort darauf hingewiesen, dies hat nichts speziell mit einem Gewinde zu tun

War es hilfreich?

Lösung

Edit: diese alle Änderungen in C # 5, mit einer Änderung zu, wo die Variable (in den Augen des Compilers) definiert ist. C # 5 ab, sie sind die gleichen .


Vor der C # 5

Die zweite ist sicher; die erste ist, nicht.

Mit foreach wird die Variable deklariert außerhalb die Schleife - d. H

Foo f;
while(iterator.MoveNext())
{
     f = iterator.Current;
    // do something with f
}

Das bedeutet, dass es nur 1 f in Bezug auf die Schließung Umfang ist, und die Fäden könnten sehr wahrscheinlich verwirrt - die Methode mehrfach auf einigen Fällen und schon gar nicht auf andere aufrufen. Sie können dieses Problem beheben mit einer zweiten Variablendeklaration innen die Schleife:

foreach(Foo f in ...) {
    Foo tmp = f;
    // do something with tmp
}

Dies hat dann einen separaten tmp in jedem Verschluss Umfang, so besteht keine Gefahr dieser Ausgabe.

Hier ist ein einfacher Beweis des Problems:

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

Ausgänge (zufällig):

1
3
4
4
5
7
7
8
9
9

eine temporäre Variable hinzufügen und es funktioniert:

        foreach (int i in data)
        {
            int j = i;
            new Thread(() => Console.WriteLine(j)).Start();
        }

(jede Zahl nur einmal, sondern natürlich auch die Reihenfolge ist nicht garantiert)

Andere Tipps

Pop Catalin und Marc GRA Die Antworten sind richtig. Alles, was ich ist ein Link zu meinem Artikel über Schließungen (die über beiden Java sprechen hinzufügen möchte und C#). Ich dachte, es könnte ein wenig Mehrwert.

EDIT: Ich denke, es lohnt sich ein Beispiel zu geben, die nicht die Unberechenbarkeit des Durchzugs hat. Hier ist ein kurzes, aber vollständiges Programm beiden Ansätze zeigt. Die „schlechte Aktion“ Liste ausdruckt 10 zehnmal; die „gute Aktion“ Liste 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();
        }
    }
}

Ihre Notwendigkeit zu verwenden, Option 2, einen Verschluss um eine sich verändernde Variable zu schaffen wird, den Wert der Variablen verwenden, wenn die Variable verwendet wird und nicht bei der Schliessung Erstellungszeit.

Die Umsetzung der anonymen Methoden in C # und ihre Folgen (Teil 1)

Die Umsetzung der anonymen Methoden in C # und ihre Folgen (Teil 2)

Die Umsetzung der anonymen Methoden in C # und ihre Folgen (Teil 3)

Edit: um deutlich zu machen, in C # Verschlüsse sind „ lexikalische Verschlüsse “ bedeutet, dass sie nicht den Wert einer Variablen erfassen, sondern die Variable selbst. Das bedeutet, dass, wenn ein Verschluss an eine sich verändernde Variable Schaffung der Verschluss ist eigentlich ein Verweis auf die Variable nicht eine Kopie davon ist Wert.

Edit2:. Hinzugefügt Links zu allen Blog-Posts, wenn jemand interessiert ist über Compiler-Interna beim Lesen

Dies ist eine interessante Frage, und es scheint, wie wir Menschen in allen verschiedenen Arten beantworten haben gesehen. Ich hatte den Eindruck, dass der zweite Weg der einzig sichere Weg wäre. Ich peitschte einen echten schnellen Beweis:

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 Sie werden sehen, die Option 1 ist auf jeden Fall nicht sicher.

In Ihrem Fall können Sie das Problem vermeiden, ohne das Kopieren Trick durch Abbildung Ihrer ListOfFoo auf eine Folge von Fäden:

var threads = ListOfFoo.Select(foo => new Thread(() => foo.DoSomething()));
foreach (var t in threads)
{
    t.Start();
}

Beide sind sicher wie von C # Version 5 (.NET Framework 4.5). Sehen Sie diese Frage für Details: Hat foreach die Nutzung von Variablen in C # 5?

geändert
Foo f2 = f;

Punkte auf die gleiche Referenz wie

f 

So nichts verloren und nichts gewonnen ...

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top