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 将一个项目添加到基础字典。Thread1 是否受 Thread2 的添加影响?

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

好的,我做了一些测试,得到了一个有趣的结果。

似乎它更多的是基础集合的枚举器而不是yield关键字的问题。枚举器(实际上是它的MoveNext方法)抛出(如果实现正确),InvalidOperationException因为枚举已更改。根据MoveNext 方法的 MSDN 文档,这是预期的行为。

因为通过集合枚举通常不是线程安全的,所以 ayield 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 的新线程本地 * 实例,因此不应存在两个线程可能发生冲突的任何“共享”内存状态......

  • 线程本地 - 从某种意义上说,它的引用变量的范围是该线程上的方法堆栈帧
于 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 回答