如何使 ObservableCollection 线程安全?

Posted

技术标签:

【中文标题】如何使 ObservableCollection 线程安全?【英文标题】:How to make ObservableCollection thread-safe? 【发布时间】:2014-05-31 05:50:37 【问题描述】:
System.InvalidOperationException: Collection was modified; enumeration operation may not execute.

我正在添加/删除不在 UI 线程上的 ObservableCollection。

我有一个方法名称 EnqueueReport 要添加到集合中,还有一个 DequeueReport 要从集合中删除。

步骤流程如下:-

    1.在请求新报告时调用 EnqueueReport 每隔几秒调用一个方法来检查报告是否生成(这有一个 foreach 循环检查 ObservableCollection 中所有报告的生成状态) 如果生成报告,则调用 DequeueReport

我对 C# 库的了解不多。有人可以指导我吗?

【问题讨论】:

试试这个ObservableCollection and threading 使用 .net 框架,您无法绑定到可观察集合,然后在 UI 线程之外对其进行编辑。围绕这一点有一些模式,包括延迟 UI 更新、批量插入或创建 CollectionView 的线程安全类实现。 【参考方案1】:

从 .net 框架 4.5 开始,您可以使用本机集合同步。

BindingOperations.EnableCollectionSynchronization(YourCollection, YourLockObject);

YourLockObject 是任何对象的实例,例如new Object();。每个集合使用一个。

这消除了一些特殊类或任何东西的需要。只需启用并享受 ;)

[编辑] 正如 Mark 和 Ed 在 cmets 中所述(感谢您的澄清!),这确实不会使您不必将集合锁定在更新上,因为它只是同步了集合视图绑定并且没有 神奇地使集合本身成为线程安全的。 [/edit]

PS:BindingOperations 驻留在命名空间 System.Windows.Data

【讨论】:

请注意,这允许从其他线程更改集合,但如果您从多个线程访问集合,您仍然需要进行同步。 IE。集合不是突然线程安全的,但 WPF 中的绑定是。 要添加到@MarkGjøl 的评论“从多个线程访问...”:您需要围绕任何可能从某些线程调用的代码执行lock (YourLockObject) .. UI 线程以外的线程。也就是说,即使 you 是“仅从 one 线程调用”,也要清楚 UI 线程已经参与,所以如果你(或可能)在某些其他线程,则需要锁定。【参考方案2】:

Franck 在此处发布的解决方案适用于一个线程正在添加内容的情况,但 ObservableCollection 本身(以及它所基于的 List)不是线程安全的。如果多个线程正在写入集合,则可能会引入难以追踪的错误。我编写了一个 ObservableCollection 版本,它使用 ReaderWriteLockSlim 来实现真正的线程安全。

Unfortunately, it hit the *** character limit, so here it is on PasteBin. 这应该 100% 与多个读取器/写入器一起工作。就像常规的 ObservableCollection 一样,在回调中修改集合是无效的(在收到回调的线程上)。

【讨论】:

哇,你全力以赴!这是完美的。谢谢。 @jnm2 - 我认为我之前提出的版本没有编译;上传了一个不引用任何内部实用程序函数的新函数。 哈哈,我也是。不用担心。 可能存在竞争条件;经过数小时的运行后,我总是得到“一个 ItemsControl 与其项目源不一致”...“累计计数是(上次重置时的计数 + #Adds - 自上次重置以来的 #Removes)。” @wonkorealtime 如果我正确理解情况和代码,实际上存在您提到的竞争条件:WPF 期望“对集合的更改和该更改的通知(通过 INotifyCollectionChanged)是原子的; 其他线程的访问不能干预” (docs.microsoft.com/en-us/dotnet/api/…) 示例:工作线程执行 Add,将事件排队,UI 线程读取 Count,接收事件并发现不一致。【参考方案3】:

您可以创建可观察集合的简单线程友好版本。像下面这样:

 public class MTObservableCollection<T> : ObservableCollection<T>
    
        public override event NotifyCollectionChangedEventHandler CollectionChanged;
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        
            NotifyCollectionChangedEventHandler CollectionChanged = this.CollectionChanged;
            if (CollectionChanged != null)
                foreach (NotifyCollectionChangedEventHandler nh in CollectionChanged.GetInvocationList())
                
                    DispatcherObject dispObj = nh.Target as DispatcherObject;
                    if (dispObj != null)
                    
                        Dispatcher dispatcher = dispObj.Dispatcher;
                        if (dispatcher != null && !dispatcher.CheckAccess())
                        
                            dispatcher.BeginInvoke(
                                (Action)(() => nh.Invoke(this,
                                    new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
                                DispatcherPriority.DataBind);
                            continue;
                        
                    
                    nh.Invoke(this, e);
                
        
    

现在进行大量查找和替换并将您所有的 ObservableCollection 更改为 MTObservableCollection 和您的好去处

【讨论】:

这不是线程安全的,因为 ObservableCollection 不是线程安全的。它可能在大多数情况下都有效,但如果多个线程正在写入它,您迟早会遇到重入异常或损坏数据,这可能是一个真正难以追踪的问题. 通常你不会让线程直接写入/添加到非线程对象中。当新项目正在处理并准备好收集时,您将使用进度事件来更新线程信息。然后主线程可以异步调用该列表中的项目插入。此列表通过调度程序进行异步更新。但我不知道你是如何使用它的,但我经常使用它,我不断地每秒推拉 2,250 到 2,525 个项目,上面运行 25 个线程。每次推或拉每秒 90 到 101 件物品。它运行 24/7,仍然没有问题。 这绝不是线程安全的,您所做的只是切换到 UI 线程/调度程序。没有使收集线程安全。 不是线程安全的,但它确实回答了 OP 的问题。也许这个问题应该重新命名为“如何从不同的线程安全地添加到 UI 线程的 ObservableCollection”。无论如何,这就是我在搜索“线程安全的 observablecollection”时所寻找的。​​span> @shtse8 问题不在于线程安全收集。它是关于从另一个线程访问集合,也就是跨线程访问。调度程序本身是线程安全的,但这使得这种实现线程友好而不是线程安全。【参考方案4】:

您可以使用 ObservableConcurrentCollection 类。它们位于 Microsoft 在 Parallel Extensions Extras 库中提供的包中。

您可以在 Nuget 上由社区预构建它: https://www.nuget.org/packages/ParallelExtensionsExtras/

或在此处从 Microsoft 获取:

https://code.msdn.microsoft.com/ParExtSamples

【讨论】:

ObservableConcurrentCollection 的功能有些受限。更好的实现在这里codeproject.com/Articles/64936/…

以上是关于如何使 ObservableCollection 线程安全?的主要内容,如果未能解决你的问题,请参考以下文章

ObservableCollection 类

如何将标签的 ItemsControl 绑定到 ObservableCollection

如何通过工作线程更新 ObservableCollection?

如何将 ObservableCollection 绑定到 DataTemplate 中的文本框?

如何将 WPF DataGrid 绑定到 ObservableCollection

如何在 ObservableCollection 对象中添加新数据?