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

我有以下代码:

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

这是线程安全的吗? 如果不是,我必须lock循环或yield return

这就是我的意思:

Thread1访问Keys属性,而Thread2将一个项添加到基础字典。 Thread1是否受Thread2的影响?

什么是线程安全的意思?

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

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

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

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

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

在C#yield关键字的幕后

简而言之 – 编译器接受yield关键字并在IL中生成整个类以支持该function。 您可以在跳转后查看页面并查看生成的代码…并且该代码看起来像跟踪线程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 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 _collection = new List(); private object _sync = new object(); public void Add(int i) { lock(_sync) { _collection.Add(i); } } public IEnumerable AllValues { get { lock (_sync) { foreach (int i in _collection) { yield return i; } } } } }