在多线程观察者模式中使用什么集合?

时间:2014-07-17 06:28:16

标签: c# multithreading concurrency observer-pattern

我目前正在实施Observer设计情况。

我的主要班级中有注册观察员名单。

private static volatile List<IObserver> registeredObservers;

每个Observer都是一个网络套接字,当套接字连接好并且一切都很好时,它会将其自身注册到可观察类。如果有错误或套接字只是断开连接,那么从observable中取消注册/删除它自己。

这一切似乎运作良好。我很高兴。

但现在在我的主课程中,我循环遍历所有已注册的观察者,如下所示:

private void SendEventToObservers(ILogItem item)
{
    foreach (var observer in registeredObservers)
    {
        if (observer != null)
        {
            observer.OnMessageRecieveEvent(new ObserverEvent(item));
        }
    }
}

然后抛出以下错误:

Collection was modified; enumeration operation may not execute

现在我已经阅读了另一个SO线程,我需要更改每个以添加.ToList():

foreach (var observer in registeredObservers.ToList()) 

现在这并没有完全解决我的问题,因为如果Observer在忙碌循环时从列表中删除了自己,.toList()会创建列表的“旧”表示吗?

我在想,是否会有更好的并发列表在被访问时以及添加和删除观察者时会阻塞?

或者当前.ToList()解决方案是否有效?

2 个答案:

答案 0 :(得分:3)

既然您已经提到block when being accessed and when observers are being added and removed可以,我认为同步线程是最安全的选择。

如果您没有运行耗时的操作,这将是最佳解决方案。只需lock列表并执行操作:

添加:

lock (registeredObservers)
    registeredObservers.Add(newObserver);

卸下:

lock (registeredObservers)
    registeredObservers.Remove(outOfServiceObserver);

迭代:

lock (registeredObservers)
    foreach (var observer in registeredObservers)
    {
        if (observer != null)
        {
            observer.OnMessageRecieveEvent(new ObserverEvent(item));
        }
    }

更新:

@fourpastmidnight提出了这个解决方案的一个严重问题,我认为编辑更安全的答案更好:迭代列表的副本

lock (registeredObservers)
    foreach (var observer in registeredObservers.ToArray())
    {
        if (observer != null)
        {
            observer.OnMessageRecieveEvent(new ObserverEvent(item));
        }
    }

答案 1 :(得分:1)

使用使用枚举器的foreach循环时,您无法修改枚举的集合的内容。

因此,当您枚举套接字集合时,套接字将断开连接并从列表中删除(或者打开一个新套接字并将其添加到列表中)。由于您的列表正在多线程场景中使用,因此当您对列表进行枚举并发生异常时会发生这种情况。 MSDN文档非常清楚标准.NET集合不是线程安全的。

您需要使用位于System.Collections.Concurrent命名空间中的线程安全集合,例如System.Collections.Concurrent.ConcurrentBag<T>System.Collections.Concurrent.BlockingCollection<T>,具体取决于您的需求。

相关问题