如何使ObservableCollection线程安全?

ili*_*ght 18 c# observablecollection thread-safety xamarin

System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
Run Code Online (Sandbox Code Playgroud)

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

我有一个方法名称EnqueueReport添加到colleciton和DequeueReport从集合中删除.

步骤流程如下: -

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

我在C#库中并不多.有人可以指导我吗?

Fas*_*ack 27

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

BindingOperations.EnableCollectionSynchronization(YourCollection, YourLockObject);

YourLockObject是任何对象的实例,例如new Object();.每个系列使用一个.

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

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

  • 请注意,这将启用来自其他线程的集合更改,但是如果要从多个线程访问集合,则仍需要进行同步。就是 集合不是突然线程安全的,但是WPF中的绑定是安全的。 (2认同)

Rob*_*ser 19

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

不幸的是,它达到了StackOverflow字符限制,因此它在PasteBin上.这应该与多个读者/作者100%一起工作.就像常规的ObservableCollection一样,在回调中修改集合(在收到回调的线程上)是无效的.

  • @wonkorealtime如果我正确理解了情况和代码,实际上存在您提到的竞争条件:WPF期望“对集合的更改和该更改的通知(通过INotifyCollectionChanged)是原子的;来自其他线程的访问不能干预" (https://learn.microsoft.com/en-us/dotnet/api/system.windows.data.bindingoperations.enablecollectionsynchronization?view=netframework-4.8) 示例:工作线程执行添加、对事件进行排队、UI 线程读取 Count、接收事件并发现不一致。 (2认同)

Fra*_*nck 10

您可以创建一个简单的线程友好版本的observable集合.如下:

 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);
                }
        }
    }
Run Code Online (Sandbox Code Playgroud)

与现在做一个巨大的查找和替换和更改所有你ObservableCollectionMTObservableCollection和你的好去

  • 这不是线程安全的,因为ObservableCollection <T>不是线程安全的.它可能在大多数情况下都可以工作,但是如果多个线程正在写入它,那么你迟早会得到重新出现异常或损坏数据,这可能是一个非常难以追踪的问题. (14认同)
  • 通常,您不会将线程直接写入/添加到非线程对象中.在处理新项目并准备收集时,您将使用progress事件来更新线程信息.然后主线程可以异步调用该列表中项目的插入.此列表通过调度程序执行异步更新.但是我不知道你是如何使用它的,但是我使用它并且我不断推送和拉动2,250到2,525件物品,每秒运行25个线程.每次推拉或拉动每秒90到101件物品.它全天候运行,仍然没有问题. (4认同)
  • 这绝不是线程安全的,你所做的只是切换到UI线程/ Dispatcher.不使收集线程安全. (3认同)
  • 不是线程安全的,但它确实回答了OP的问题.也许这个问题应该重新命名为"如何从不同的线程安全地添加到UI线程的ObservableCollection".无论如何,当我搜索"thread safe observablecollection"时,这就是我所寻找的. (2认同)

Gre*_*reg 10

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

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

或者从Microsoft获取它:

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