我有以下代码:
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的添加影响?
答案 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;
}
}
}
}
}