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