C#中的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;
}
}
}
这是线程安全的吗?如果不是,我是否必须在循环或收益返回周围放置一个锁
?
我的意思是:
Thread1 访问 Keys 属性,而 Thread2 向底层字典添加一个项目。 Thread1 是否受到 Thread2 的添加的影响?
I have the following piece of code:
private Dictionary<object, object> items = new Dictionary<object, object>;
public IEnumerable<object> Keys
{
get
{
foreach (object key in items.Keys)
{
yield return key;
}
}
}
Is this thread-safe? If not do I have to put a lock
around the loop or the yield return
?
Here is what I mean:
Thread1 accesses the Keys
property while Thread2 adds an item to the underlying dictionary. Is Thread1 affected by the add of Thread2?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
线程安全到底是什么意思?
您当然不应该在迭代字典时更改字典,无论是否在同一线程中。
如果通常在多个线程中访问字典,调用者应该取出一个锁(覆盖所有访问的同一个锁),以便他们可以在迭代结果期间锁定。
编辑:为了回应您的编辑,不,它绝不对应于锁定代码。迭代器块不会自动取出锁 - 它如何知道
syncRoot
呢?此外,仅锁定
IEnumerable
的返回也不会使其成为线程安全的 - 因为锁定仅影响它返回序列的时间段,而不是迭代的时间段。What exactly do you mean by thread-safe?
You certainly shouldn't change the dictionary while you're iterating over it, whether in the same thread or not.
If the dictionary is being accessed in multiple threads in general, the caller should take out a lock (the same one covering all accesses) so that they can lock for the duration of iterating over the result.
EDIT: To respond to your edit, no it in no way corresponds to the lock code. There is no lock automatically taken out by an iterator block - and how would it know about
syncRoot
anyway?Moreover, just locking the return of the
IEnumerable<TKey>
doesn't make it thread-safe either - because the lock only affects the period of time when it's returning the sequence, not the period during which it's being iterated over.查看这篇文章,了解
yield
关键字在幕后发生的情况:C# Yield 关键字的幕后
简而言之 - 编译器采用您的 Yield 关键字并在 IL 中生成整个类来支持该功能。您可以查看跳转后的页面并查看生成的代码...该代码看起来像是跟踪线程 ID 以确保安全。
Check out this post on what happens behind the scenes with the
yield
keyword:Behind the scenes of the C# yield keyword
In short - the compiler takes your yield keyword and generates an entire class in the IL to support the functionality. You can check out the page after the jump and check out the code that gets generated...and that code looks like it tracks thread id to keep things safe.
好的,我做了一些测试并得到了一个有趣的结果。
看起来这更多是底层集合的枚举器的问题,而不是
yield
关键字的问题。枚举器(实际上是其MoveNext
方法)会抛出(如果正确实现)InvalidOperationException
,因为枚举已更改。根据 MoveNext 方法的 MSDN 文档这是预期的行为。因为通过集合进行枚举通常不是线程安全的,所以
yield return
也不是。OK, I did some testing and got an interesting result.
It seems that it is more an issue of the enumerator of the underlying collection than the
yield
keyword. The enumerator (actually itsMoveNext
method) throws (if implemented correctly) anInvalidOperationException
because the enumeration has changed. According to the MSDN documentation of the MoveNext method this is the expected behavior.Because enumerating through a collection is usually not thread-safe a
yield return
is not either.我相信是这样,但我找不到证实这一点的参考文献。每次任何线程在迭代器上调用 foreach 时,都应创建底层 IEnumerator 的新线程 local* 实例,因此不应存在两个线程可能发生冲突的任何“共享”内存状态...
I believe it is, but I cannot find a reference that confirms it. Each time any thread calls foreach on an iterator, a new thread local* instance of the underlying IEnumerator should get created, so there should not be any "shared" memory state that two threads can conflict over...
我相信yield实现是线程安全的。事实上,您可以在家运行这个简单的程序,您会注意到 listInt() 方法的状态已为每个线程正确保存和恢复,而没有其他线程的边缘效应。
I believe yield implementation is thread-safe. Indeed, you can run that simple program at home and you will notice that the state of the listInt() method is correctly saved and restored for each thread without edge effect from other threads.