31

次のコードがあります。

private Dictionary<object, object> items = new Dictionary<object, object>;
public IEnumerable<object> Keys
{
    get
    {
        foreach (object key in items.Keys)
        {
            yield return key;
        }
    }
}

これはスレッドセーフですか?そうでない場合はlock、ループの周りに配置する必要がありyield returnますか?

これが私が意味することです:

Thread1 はKeysプロパティにアクセスし、Thread2 は基になるディクショナリに項目を追加します。Thread2 の追加によって Thread1 は影響を受けますか?

4

6 に答える 6

20

スレッドセーフとは正確にはどういう意味ですか?

同じスレッド内であろうとなかろうと、反復処理中に辞書を変更するべきではありません。

一般に、ディクショナリが複数のスレッドでアクセスされている場合、呼び出し元はロック (すべてのアクセスをカバーする同じもの) を取得して、結果の反復中にロックできるようにする必要があります。

編集: あなたの編集に対応するには、いいえ、ロック コードに対応するものではありません。イテレータ ブロックによって自動的に取り出されるロックはありませsyncRootん。

さらに、の戻り値をロックするだけでは、IEnumerable<TKey>スレッドセーフにはなりません。ロックは、シーケンスが繰り返される期間ではなく、シーケンスを返す期間にのみ影響するためです。

于 2009-09-04T13:34:02.363 に答える
18

yieldキーワードで舞台裏で何が起こるかについてのこの投稿をチェックしてください:

C#のyieldキーワードの舞台裏

つまり、コンパイラはyieldキーワードを受け取り、機能をサポートするためにILにクラス全体を生成します。ジャンプの後にページをチェックアウトして、生成されるコードをチェックアウトすることができます...そしてそのコードは、物事を安全に保つためにスレッドIDを追跡しているように見えます。

于 2009-09-04T13:34:57.027 に答える
12

OK、いくつかのテストを行ったところ、興味深い結果が得られました。

yieldキーワードよりも、基になるコレクションの列挙子の問題のようです。列挙子 (実際にはそのメソッド) は、列挙が変更されMoveNextたため、(正しく実装されている場合) をスローします。MoveNext メソッドInvalidOperationExceptionの MSDN ドキュメントによると、これは予想される動作です。

通常、コレクションの列挙はスレッドセーフではないため、 aもそうでyield returnはありません。

于 2009-09-04T16:41:29.800 に答える
4

yield の実装はスレッドセーフだと思います。実際、この単純なプログラムを自宅で実行すると、listInt() メソッドの状態がスレッドごとに正しく保存され、他のスレッドからのエッジ効果なしで復元されることがわかります。

public class Test
{
    public void Display(int index)
    {
        foreach (int i in listInt())
        {
            Console.WriteLine("Thread {0} says: {1}", index, i);
            Thread.Sleep(1);
        }

    }

    public IEnumerable<int> listInt()
    {
        for (int i = 0; i < 5; i++)
        {
            yield return i;
        }
    }
}

class MainApp
{
    static void Main()
    {
        Test test = new Test();
        for (int i = 0; i < 4; i++)
        {
            int x = i;
            Thread t = new Thread(p => { test.Display(x); });
            t.Start();
        }

        // Wait for user
        Console.ReadKey();
    }
}
于 2011-09-11T23:31:12.170 に答える
3

そうだと思いますが、それを裏付ける参考資料が見つかりません。スレッドがイテレータでforeachを呼び出すたびに、基になるIEnumeratorの新しいスレッドローカル*インスタンスが作成される必要があるため、2つのスレッドが競合する可能性のある「共有」メモリ状態があってはなりません...

  • スレッドローカル-参照変数がそのスレッドのメソッドスタックフレームにスコープされているという意味で
于 2009-09-04T13:39:00.080 に答える
2
class Program
{
    static SomeCollection _sc = new SomeCollection();

    static void Main(string[] args)
    {
        // Create one thread that adds entries and
        // one thread that reads them
        Thread t1 = new Thread(AddEntries);
        Thread t2 = new Thread(EnumEntries);

        t2.Start(_sc);
        t1.Start(_sc);
    }

    static void AddEntries(object state)
    {
        SomeCollection sc = (SomeCollection)state;

        for (int x = 0; x < 20; x++)
        {
            Trace.WriteLine("adding");
            sc.Add(x);
            Trace.WriteLine("added");
            Thread.Sleep(x * 3);
        }
    }

    static void EnumEntries(object state)
    {
        SomeCollection sc = (SomeCollection)state;
        for (int x = 0; x < 10; x++)
        {
            Trace.WriteLine("Loop" + x);
            foreach (int item in sc.AllValues)
            {
                Trace.Write(item + " ");
            }
            Thread.Sleep(30);
            Trace.WriteLine("");
        }
    }
}

class SomeCollection
{
    private List<int> _collection = new List<int>();
    private object _sync = new object();

    public void Add(int i)
    {
        lock(_sync)
        {
            _collection.Add(i);
        }
    }


    public IEnumerable<int> AllValues
    {
        get
        {
            lock (_sync)
            {
                foreach (int i in _collection)
                {
                    yield return i;
                }
            }
        }
    }
}
于 2012-10-24T17:30:08.823 に答える