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



我目前正在实现一个观察者设计的情况。

我在我的主类中有我的注册观察者列表。

private static volatile List<IObserver> registeredObservers;

每个观察者都是一个网络套接字,当套接字连接好并且一切正常时,它将自己注册到可观察类中。如果出现错误或套接字只是断开连接,则从可观察对象中注销/删除其自身。

这一切似乎都工作得很好。我很高兴。

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

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()) 

现在这并不能完全解决我的问题,因为如果观察者在忙于循环时从列表中删除自己,那么.toList()会创建列表的"旧"表示?

我在想,会不会有一种更好的并发列表,在被访问时可以阻塞,当观察者被添加和删除时?

或者当前的。tolist()解决方案是否有效?

既然你已经提到它是OK的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));
        }
    }

当使用foreach循环时,它使用枚举器,您不能修改被枚举的集合的内容。

所以看起来像是在枚举套接字集合时,一个套接字断开连接并从列表中删除(或者打开一个新的套接字并将其添加到列表中)。由于您的列表是在多线程场景中使用的,因此在枚举列表时就会发生这种情况,并且会发生异常。MSDN文档非常清楚地指出,标准的。net集合不是线程安全的。

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

相关内容

  • 没有找到相关文章

最新更新