枚举器究竟是如何工作的 - 我知道他们在幕后构建一个状态机但是如果我两次调用GetEnumerator会得到两个不同的对象吗?
如果我这样做
public IEnumerator<T> GetEnumerator()
{
yield return 1;
yield return 2;
}
我可以在方法的开头获取一个锁,并且这个锁是否一直保持到枚举器返回null或枚举器是否为GC?
如果调用者重置枚举器等会发生什么 -
我想我的问题是在处理枚举器时管理锁定的最佳方法是什么
注意:客户端不能负责线程同步 - 该类内部需要
最后上面的例子是问题的简化 - yield语句比我显示的更多:)
答案 0 :(得分:8)
是的,每次调用GetEnumerator()
方法都会创建一个不同的对象(一个新的状态机)。
你可以获取迭代器块中的锁,但请注意,在调用者第一次调用MoveNext()
之前,不会调用方法中的任何代码。
一般情况下,如果可能的话,我会建议反对在迭代器块中持有一个锁。你不知道调用MoveNext()
之间调用者会做什么。只要他们在某个时候处理迭代器,锁最终会被释放,但它仍然意味着你受到调用者的支配。
如果您可以向我们提供有关您尝试做什么的更多信息,那将有所帮助。 可能更容易正确的替代设计将是:
public void DoSomething(Action<T> action)
{
lock (...)
{
// Call action on each element in here
}
}
答案 1 :(得分:2)
正如约翰已经说过很难给你一个好的答案。 明显的谷歌搜索导致:http://www.codeproject.com/KB/cs/safe_enumerable.aspx
这背后的想法是将IEnumerable实例锁定在具有主要缺点的构造上。
下一个显而易见的事情是隔离,您可以在其中创建结构的副本并遍历副本。这是天真地实现非常耗费内存但如果您的数据集相对较小则值得。
最好的事情是,如果您的数据是不可变的,那么您具有自动线程安全性,但如果您绑定到计数确实发生更改的集合,则您具有可变数据结构。如果您可以将数据结构重新设计为不可变数据结构,那么您就完成了。
由于长时间锁定数据不是一个好主意,因此当您利用精确的数据结构和用例时,可以实施策略来实现线程安全。例如,如果您很少更改数据并经常枚举它,则可以实现一个乐观枚举,它在枚举开始之前读取数据结构的写计数器并像往常一样产生结果。如果在两者之间发生写入,您可以抛出异常来通知您的枚举器用户再次尝试,直到他成功为止。这确实有效,但将责任委托给您的枚举的调用者,他需要重试枚举直到成功为止。