繁体   English   中英

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

[英]Is yield return in C# thread-safe?

我有以下代码:

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

什么是线程安全的意思?

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

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

编辑:要响应您的编辑,它不会与锁定代码对应。 迭代器块没有自动取出锁定 - 无论如何它都会知道syncRoot

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

查看这篇文章,了解使用yield关键字在幕后发生的事情:

在C#yield关键字的幕后

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

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM