1. ホーム
  2. c#

foreach識別子とクロージャ

2023-08-27 05:27:58

質問

以下の2つのスニペットにおいて、最初のスニペットは安全ですか、それとも2番目のスニペットを実行しなければなりませんか?

安全というのは、各スレッドが、スレッドが作成されたのと同じループのイテレーションからFoo上のメソッドを呼び出すことが保証されているかということです。

それとも、ループの各反復に新しい変数 "local" への参照をコピーしなければならないのでしょうか?

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

更新しました。 Jon Skeetの回答で指摘されているように、これは特にスレッディングとは関係ありません。

どのように解決するのですか?

編集:これはすべて C# 5 で変更され、変数が(コンパイラから見て)定義される場所が変更されました。以下から C# 5 以降では、それらは同じ .


C#5以前

2番目は安全で、1番目は安全ではありません。

とは foreach で、変数が宣言されています。 ループの外、つまり

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

これは、1つしかない f のみであり、スレッドが混乱する可能性があります。これを修正するには、2 つ目の変数宣言 内部 ループの内部で

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

これは次に、別の tmp をそれぞれのクロージャースコープに含めるので、この問題が発生する危険はありません。

以下は、この問題の簡単な証明です。

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

出力する(ランダムに)。

1
3
4
4
5
7
7
8
9
9

temp変数を追加すると動作します。

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

(各番号は1回ずつ。もちろん順番は保証されていない)