我在与正在开发的多人同步游戏同时使用共享集合时遇到问题。我进行了一些挖掘,并在 Alexey Drobyshevsky 关于 codeproject 的帖子中找到了一个简洁的线程安全 IEnumerator/IList 实现:
http://www.codeproject.com/KB/cs/safe_enumerable.aspx
在采用他的实现后,我什至用 for/foreach 循环替换了共享集合上的所有 Linq 查询,因为 Linq 查询仍在使用底层的不安全 IEnumerable。
这是我对 SafeList 的实现,列表本身作为 ReadOnlyCollection 暴露给使用类。
http://theburningmonk.com/2010/03/thread-safe-enumeration-in-csharp/
切换到此 SafeList 后,我看到的问题少得多,但在重负载下(80 多个线程,所有线程都在不同的点从列表读取/写入)我仍然看到 InvalidOperationException 被抛出:
The element list has changed. The enumeration operation failed to continue
我什至尝试在 SafeList 的实现中使用 ReadWriterLockSlim 代替锁定对象,但结果也没有结果。到目前为止,我唯一的其他建议是每次线程需要循环时克隆列表。我希望避免每次都克隆该列表,因为该列表在太多地方使用,这可能会影响性能并可能引入其他难以发现的错误。
考虑到时间限制,我必须务实一点,如果克隆是解决这个问题最安全、最快捷的方法,那么我可以接受,但在诉诸最后的尝试之前,我'我只是想知道是否有人遇到过类似的事情能够提供一些建议。
非常感谢!
[编辑] 以下是我按要求看到的问题的更多信息:
对于一个“游戏”,最多可以有 100 个左右的同步客户端连接,游戏需要每隔几秒向每个连接的客户端发送更新消息,因此每隔几秒这个游戏需要迭代玩家的共享列表. 当玩家加入或离开时,列表需要相应地更新以反射(reflect)变化。 除此之外,玩家可以与游戏互动并与其他玩家聊天,每次收到玩家的消息时,游戏都需要再次遍历同一个列表并进行广播。 当游戏尝试向玩家广播消息(读取操作),同时许多玩家同时离开/加入(写入操作)时,通常会抛出异常。
最佳答案
根据您对游戏结构的描述,考虑使用一个线程作为唯一可以直接访问玩家列表的线程。使列表对那个线程有效私有(private)。
任何其他线程访问列表的方式是向列表管理器线程发送消息。因此该线程有一个等待的消息队列。当队列非空时,它会按照消息的指示咀嚼消息。他们可能会说“添加新玩家”或“移除玩家”或“将此玩家的状态更新为‘不开心’”。
该线程还可以定期扫描列表以将更新发送给客户端,或者它可以(甚至更好)利用它具有列表正在发生的已知更改流这一事实,并将这些更改转发到客户。
基本原则:将数据私有(private)化为一个线程,线程之间通过消息队列进行通信。
您的基本数据结构是一个线程安全的队列类。 SO 上已经有许多示例。 (并避开任何声称“无锁”但线程安全的东西。不值得冒这个风险。)
关于c# - 解决集合的并发问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2383446/