WPF 解决 ObservableCollection 提示 Cannot change ObservableCollection during a CollectionChanged event 异常

本文告诉大家在使用 ObservableCollection 时,抛出 InvalidOperationException 异常,提示 Cannot change ObservableCollection during a CollectionChanged event 内容,的原因和解决方法

准确来说,这个异常和 WPF 是没有任何关系的。这个异常是 ObservableCollection 类型抛出的,而 ObservableCollection 类型是在 dotnet runtime 定义的,放在 System.ObjectModel 里,而且此异常可以在除 WPF 的其他框架,比如控制台或者 UWP 上复现

想要解决此问题,还请先了解一下此异常抛出的原因

在 ObservableCollection 的设计上,是可以了解列表的变更。而在列表的变更了解,是通过 CollectionChanged 事件实现。然而事件的触发,稍微了解 C# 语法的开发者都知道,是每个方法独立执行。这就让 ObservableCollection 存在一个设计上需要解决的问题,那就是如果事件 CollectionChanged 被加等两次,意味着有两次方法的调用。如果在第一次调用方法时,在此方法内再次修改了 ObservableCollection 列表的元素,那么将会让第二个方法进入的时候,所获取的状态和第一个方法所获取的一定不相同

这个设计上的问题,是很难解决的。既然很难解决,那就不解决了,将问题交给开发者好了,在 ObservableCollection 判断如果 CollectionChanged 事件被加等大于 1 次,同时在事件触发的过程中,进行集合的变更,将会抛出 InvalidOperationException 异常,提示 Cannot change ObservableCollection during a CollectionChanged event 内容

这就是从设计上的原因。那为什么只加等 1 次时不抛出呢?那是因为既然只有一次,那改不改都影响不了当前的进入的方法的状态

由于 CollectionChanged 事件加等的次数决定了 InvalidOperationException 是否抛出,从而让一些开发者拿到错误的结论: 在 CollectionChanged 事件里面修改集合本身是安全的。或者反过来,在 CollectionChanged 事件里面修改集合本身是不安全的

正确的行为是: 当 CollectionChanged 事件加等的委托在 1 个以内时,在 CollectionChanged 事件里面修改集合本身是安全的。如果 CollectionChanged 事件加等的委托大于 1 个时,在 CollectionChanged 事件里面修改集合本身是不安全的

从代码上,在 ObservableCollection 的各个更改集合的函数,例如 InsertItem ClearItems RemoveItem 等,都会调用 CheckReentrancy 方法,判断是否存在重入。在 CheckReentrancy 方法的实现如下

        ///  Check and assert for reentrant attempts to change this collection.

        ///  raised when changing the collection
        /// while another collection change is still being notified to other listeners
        protected void CheckReentrancy()
        {
            if (_blockReentrancyCount > 0)
            {
                // we can allow changes if there's only one listener - the problem
                // only arises if reentrant changes make the original event args
                // invalid for later listeners.  This keeps existing code working
                // (e.g. Selector.SelectedItems).

                if (CollectionChanged?.GetInvocationList().Length > 1)
                    throw new InvalidOperationException(SR.ObservableCollectionReentrancyNotAllowed);
            }
        }

上面代码的 _blockReentrancyCount 是在 OnCollectionChanged 方法和 BlockReentrancy 方法使用的。在没有重写 ObservableCollection 的情况下,可以认为 _blockReentrancyCount 只有在 OnCollectionChanged 方法更改

        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            NotifyCollectionChangedEventHandler? handler = CollectionChanged;
            if (handler != null)
            {
                // Not calling BlockReentrancy() here to avoid the SimpleMonitor allocation.

                _blockReentrancyCount++;
                try
                {
                    handler(this, e);
                }
                finally
                {
                    _blockReentrancyCount--;
                }
            }
        }

也就是说,在 CollectionChanged 事件触发进入的方法里面,一定是判断 if (_blockReentrancyCount > 0) 通过的。也就说接下来只需要看 if (CollectionChanged?.GetInvocationList().Length > 1) 判断即可。这里的 GetInvocationList 是 CollectionChanged 事件对应的委托的数量,只要超过 1 个就炸

了解了原因,那么解决方法也很简单。要么是在 CollectionChanged 事件里面修改集合时确保只让 CollectionChanged 加等一个委托。要么就是继承 ObservableCollection 类型,重写 OnCollectionChanged 方法,不要修改 _blockReentrancyCount 字段。要么就是等待 CollectionChanged 事件触发完成之后,通过 Dispatcher 的 InvokeAsync 方法调度出去执行

Original: https://www.cnblogs.com/lindexi/p/16733273.html
Author: lindexi
Title: WPF 解决 ObservableCollection 提示 Cannot change ObservableCollection during a CollectionChanged event 异常

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/577752/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Linux下创建桌面快捷方式

    建立一个文本文件,文件名必须以.desktop结尾,.desktop前面的作为快捷方式的名称 添加如下内容 [Desktop Entry]Encoding=UTF-8Name=Po…

    Linux 2023年6月6日
    094
  • 设计模式-单例模式

    目的:为了保证一个类在程序中只有一个实例,并且能被全局访问 场景:全局线程池 要点: 通过 Test::Instance()获取类指针 class Test { public: s…

    Linux 2023年6月8日
    093
  • 网络协议及tcp协议详解

    说说TCP三次握手的过程? 第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Ser…

    Linux 2023年6月13日
    096
  • Redis无法向磁盘写入RBD数据

    由于客户为了安全规范,规定Redis不能再root权限下运行,所以要进行降权切成普通用户等操作,刚刚做完切权操作。客户监控发出来项目服务的报错邮件,经过查看发现是Redis问题. …

    Linux 2023年6月8日
    0114
  • 全网唯一的、DIY的Prometheus高可用方案,生产未上,测试先行。

    写在开篇 关于prometheus的高可用方案,经过笔者不断的研究、对比、和搜索,发现不管是官方、还是各大搜索引擎搜索出来的方案,都不符合笔者的需求。因此,笔者自己设计了一套pro…

    Linux 2023年6月7日
    081
  • PHP PDF转图片

    Windows环境下 一、开启 Imagick 扩展 1、安装PHP扩展:Imagick,下载地址 https://pecl.php.net/package/imagick 注意和…

    Linux 2023年6月7日
    098
  • [云原生]Kubernetes-介绍(第1章)

    一、应用部署方式演变 二、Kubernetes简介 三、Kubernetes组件 四、Kubernetes概念 参考: Kubernetes(K8S) 入门进阶实战完整教程,黑马程…

    Linux 2023年6月13日
    0109
  • 数据库日志

    数据库日志: 首先,在MySQL中默认只开启了错误日志 MySQL中的日志文件: 错误日志(errorlog): 一般查询日志(general log) 慢查询日志(slow qu…

    Linux 2023年6月6日
    090
  • 【镜像取证篇】DD系统镜像仿真问题的一些补充说明

    【镜像取证篇】DD系统镜像仿真问题的一些补充说明 ​ 系统千千万,环境占一半,遇到问题建议多重新挂载镜像,多尝试,站在岸上永远学不会游泳。—【蘇小沐】 【镜像取证篇】D…

    Linux 2023年6月13日
    0124
  • 【设计模式】Java设计模式-单例模式

    【设计模式】Java设计模式 – 单例模式 😄 不断学习才是王道🔥 继续踏上学习之路,学之分享笔记👊 总有一天我也能像各位大佬一样🌝分享学习心得,欢迎指正,大家一起学习…

    Linux 2023年6月6日
    0155
  • 搭建openvpn连接公司内网

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Linux 2023年6月7日
    0119
  • RPA SAP财务内部对账机器人

    bash;gutter:true;【简介】本机器人用于使用SAP软件的集团公司间往来对账前台登录SAP账户和密码,需退出PC微信,输入法切换为英文半角状态。【详细流程】1、清空Ex…

    Linux 2023年6月7日
    0122
  • Nginx/Tengine安装配置详解

    1 概念 Nginx (“engine x”) 是一个高性能的 HTTP 和 反向代理 服务器,也是一个 IMAP/POP3/SMTP 代理服务器。官方测试…

    Linux 2023年6月14日
    093
  • 004 Linux 揭开神器 vim 面纱

    01 开篇初识 vim vim 功能吊炸天,但我们掌握一些常用的命令即可应对日常的使用了,不记流水账! Linux 中最常用的编辑器是什么? vim ! vi 跟 vim 啥区别?…

    Linux 2023年5月27日
    0107
  • MySQL(一)——查看密码与修改

    查看数据库密码,策略与修改 RPM安装: 源码安装: 进入:数据库 进入数据库后第一步设置密码: 查看密码策略 修改密码策略,长度 0宽容模式 混合模式,0关闭大小写 特殊字符 O…

    Linux 2023年6月13日
    083
  • 无线配置多一个路由器作为家庭wifi的无线热点?

    以下内容为本人的著作,如需要转载,请声明原文链接微信公众号「englyf」 https://mp.weixin.qq.com/s/8OcDnY3O6ux41GntesHHcg 手头…

    Linux 2023年6月6日
    0113
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球