C#中的收益率是否是线程安全的?

时间:2009-09-04 13:29:11

标签: c# ienumerable yield yield-return

我有以下代码:

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的添加影响?

6 个答案:

答案 0 :(得分:20)

线程安全到底是什么意思?

当你在字典上进行迭代时,无论是否在同一个线程中,你都不应该更改字典。

如果通常在多个线程中访问字典,调用者应取出一个锁(覆盖所有访问的同一个锁),以便它们可以在迭代结果的过程中锁定

编辑:要回复您的编辑,无法中的任何内容都不对应锁定代码。迭代器块没有自动取出锁定 - 无论如何它会如何知道syncRoot

此外,只是锁定IEnumerable<TKey>的返回也不会使其成为线程安全的 - 因为锁定仅影响返回序列的时间段,而不是它被迭代的时期。

答案 1 :(得分:18)

使用yield关键字查看此帖子后面发生的事情:

Behind the scenes of the C# yield keyword

简而言之 - 编译器接受yield关键字并在IL中生成一个完整的类来支持该功能。您可以在跳转后查看页面并查看生成的代码...并且该代码看起来跟踪线程ID以保证安全。

答案 2 :(得分:8)

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

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

因为通过集合枚举通常不是线程安全的,yield return也不是。

答案 3 :(得分:3)

我相信它是,但我找不到确认它的参考。每次任何线程在迭代器上调用foreach时,都应该创建底层IEnumerator的新线程本地*实例,因此不应该存在两个线程可以冲突的任何“共享”内存状态......

  • 线程本地 - 从某种意义上说,它的引用变量的作用域是该线程上的方法堆栈框架

答案 4 :(得分:3)

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

答案 5 :(得分: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;
                }
            }
        }
    }
}