WPF ObservableCollection跨线程添加报线程所有权异常
问题描述
运行时抛出异常:System.NotSupportedException: A collection's thread ownership seems changed at runtime。
业务实现代码
定义UpFile类存储监控到的文件信息,维护全局静态的文件集合:
class UpFile { public string file { get; set; } public int id { get; set; } public int long1 { get; set; } public int short1 { get; set; } static public System.Collections.ObjectModel.ObservableCollection<UpFile> _upfiles = new System.Collections.ObjectModel.ObservableCollection<UpFile>(); }
实现FileWatcher类封装文件系统监控逻辑:
class FileWatcher { public void Start(Callback callback) { _callback = callback; _watcher.Path = Config._upDirectory; _watcher.NotifyFilter = NotifyFilters.LastWrite; _watcher.Filter = "*.up"; _watcher.IncludeSubdirectories = false; _watcher.Created += new FileSystemEventHandler(OnNewFile); _watcher.Changed += new FileSystemEventHandler(OnChangeFile); _watcher.EnableRaisingEvents = true; _started = true; } private void OnChangeFile(object sender, FileSystemEventArgs args) { Logger.I("OnChangeFile " + args.FullPath); CheckFile(args.FullPath); } private void CheckFile(string fileName) { Logger.I("New UpFile:" + mbplcflow); UpFile newFile = new UpFile { id = mbplcflow, file = fileName, long1 = long1, short1 = short1 }; UpFile._upfiles.Add(newFile); _callback(); } }
回调逻辑运行在UI线程,负责将集合数据展示到DataGrid控件,定义在MainWindow类中:
private void UpFileCreated() { if (Dispatcher.CheckAccess()) dgUp.ItemsSource = UpFile._upfiles; else Dispatcher.Invoke(delegate { dgUp.ItemsSource = UpFile._upfiles; }); }
异常现象
第一次触发文件变更时逻辑运行正常,文件项可正常展示到DataGrid中;第二次触发文件变更时,在UpFile._upfiles.Add(newFile);行抛出上述异常,程序中断。
运行时采集日志如下:
I 16:47:48.169 OnChangeFile c:\test\test\23732_1657874868168.up I 16:47:48.175 New UpFile:19
核心疑问:为什么_upfiles的线程所有权会在运行时变化?如何避免集合被UI线程独占所有权?
原因分析
这个异常不是集合本身的所有权发生了变化,核心原因是WPF的数据绑定线程规则:
ObservableCollection自身没有强制线程归属校验,第一次执行Add操作时,集合还未和UI控件绑定,在FileSystemWatcher的后台线程执行修改不会触发错误。- 第一次回调执行时,集合被赋值给DataGrid的ItemsSource完成数据绑定,WPF绑定引擎会在UI线程上监听集合的
CollectionChanged事件,用于同步UI更新。WPF的UI组件天生具备线程亲和性,绑定完成后所有对集合的修改操作都必须在UI线程执行,否则绑定引擎无法安全更新UI,就会抛出该异常。 - 第二次触发文件变更时,仍然在FileSystemWatcher的后台线程执行Add操作,违反了绑定集合的线程访问规则,直接触发异常。
解决方案
WPF的UI线程亲和性是底层设计,不需要也不应该尝试绕过,直接调整代码的线程调度逻辑即可解决问题:
- 所有对绑定后
_upfiles集合的增删改操作,全部调度到UI线程执行,禁止在后台线程直接修改绑定集合。修改CheckFile方法中的Add逻辑:
private void CheckFile(string fileName) { Logger.I("New UpFile:" + mbplcflow); UpFile newFile = new UpFile { id = mbplcflow, file = fileName, long1 = long1, short1 = short1 }; // 将集合修改操作投递到UI线程执行 Application.Current.Dispatcher.Invoke(() => { UpFile._upfiles.Add(newFile); }); _callback(); }
- 移除UpFileCreated方法中重复给ItemsSource赋值的逻辑,数据绑定只需要在窗口初始化时执行一次即可,重复赋值无意义还会增加额外性能开销:
// 在MainWindow构造函数中完成一次性绑定 public MainWindow() { InitializeComponent(); dgUp.ItemsSource = UpFile._upfiles; }
- 如果后续存在高频跨线程更新集合的需求,可以自定义派生跨线程友好的ObservableCollection,在类内部自动将变更通知调度到UI线程触发;当前场景下直接调度修改操作的实现最简单、稳定性最高。
内容的提问来源于stack exchange,提问作者AIMIN PAN
相关产品推荐
相关产品推荐

