如何使 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您可以使用 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 线程安全?的主要内容,如果未能解决你的问题,请参考以下文章
如何将标签的 ItemsControl 绑定到 ObservableCollection
如何通过工作线程更新 ObservableCollection?
如何将 ObservableCollection 绑定到 DataTemplate 中的文本框?