深入了解WPF中ObservableCollection及跨线程处理ObservableCollection ...

打印 上一主题 下一主题

主题 906|帖子 906|积分 2718

WPF的ObservableCollection在增删改的时候,通过继承INotifyCollectionChanged使用CollectionChanged通过依赖属性发生了变化。(本篇的例子从:https://blog.lindexi.com/post/win10-uwp-%E9%80%9A%E7%9F%A5%E5%88%97%E8%A1%A8.html、  https://blog.lindexi.com/post/WPF-%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B8%8B%E8%B7%A8%E7%BA%BF%E7%A8%8B%E5%A4%84%E7%90%86-ObservableCollection-%E6%95%B0%E6%8D%AE.html  中引用 )
  1. public class FooList<T> : Collection<T>, INotifyCollectionChanged
  2. {
  3.     protected override void InsertItem(int index, T item)
  4.     {
  5.         base.InsertItem(index, item);
  6.         Application.Current.Dispatcher.InvokeAsync(() =>
  7.         {
  8.             CollectionChanged?.Invoke(this,
  9.           new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
  10.         });
  11.     }
  12.     protected override void RemoveItem(int index)
  13.     {
  14.         var item = this[index];
  15.         base.RemoveItem(index);
  16.         Application.Current.Dispatcher.InvokeAsync(() =>
  17.         {
  18.             CollectionChanged?.Invoke(this,
  19.           new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
  20.         });
  21.     }
  22.     protected override void SetItem(int index, T item)
  23.     {
  24.         var oldItem = this[index];
  25.         base.SetItem(index, item);
  26.         Application.Current.Dispatcher.InvokeAsync(() =>
  27.         {
  28.             CollectionChanged?.Invoke(this,
  29.           new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, oldItem, index));
  30.         });
  31.     }
  32.     public event NotifyCollectionChangedEventHandler? CollectionChanged;
  33. }
复制代码
从上面的例子可以看出,ObservableCollection集合是不需要再进行属性通知的,所以下面的写法是不需要的
  1.         public ObservableCollection<string> ObservableCollection
  2.         {
  3.             set
  4.             {
  5.                 _observableCollection = value;
  6.                 OnPropertyChanged();
  7.             }
  8.             get
  9.             {
  10.                 return _observableCollection;
  11.             }
  12.         }
  13.         private ObservableCollection<string> _observableCollection;
复制代码
在跨线程处理 ObservableCollection 数据,大多数时候都会抛出 System.NotSupportedException:“该类型的 CollectionView 不支持从调度程序线程以外的线程对其 SourceCollection 进行的更改。” 等异常。原因是ObservableCollection 是非线程安全的,因此所以在单一的时刻,只能有一个线程进行处理。如果 ObservableCollection 被 UI 元素捕获,例如加入到 ItemsSource 里面,那么此时的 ObservableCollection 不仅只能被单一线程处理,还要求这个线程是 UI 线程。针对以上情况,采取3中办法处理。
1、现在后台线程处理完ObservableCollection,再赋值给 ListView 的 ItemsSource 属性,实现更新界面逻辑。
  1. private async void Button1_Click(object sender, RoutedEventArgs e)
  2.     {
  3.         var list = await Task.Run(() =>
  4.         {
  5.             ObservableCollection<string> data = new ObservableCollection<string>();
  6.             for (int i = 0; i < 100; i++)
  7.             {
  8.                 data.Add(Random.Shared.Next(1000).ToString());
  9.             }
  10.             return data;
  11.         });
  12.         // 以上代码使用 await 等待,可以自动切回主线程
  13.         ListView.ItemsSource = list;
  14.     }
复制代码
2、在确保 UI 线程不会改动到 ObservableCollection 列表的时候,可以采用如下方法,在后台线程拷贝一份作为新的 ObservableCollection 对象,然后对此新的对象进行处理。完成之后,再将新的 ObservableCollection 对象赋值给到 UI 进行绑定。
  1. private async void Button2_Click(object sender, RoutedEventArgs e)
  2.     {
  3.         // 假定 ListView.ItemsSource 存在源了
  4.         if (ListView.ItemsSource is not ObservableCollection<string> list)
  5.         {
  6.             // 如果假设失败,强行给一个源
  7.             list = new();
  8.             ListView.ItemsSource = list;
  9.         }
  10.         var newList = await Task.Run(() =>
  11.         {
  12.             var data = new ObservableCollection<string>(list);
  13.             // 模拟对原有的列表进行处理
  14.             if (data.Count > 0)
  15.             {
  16.                 for (int i = 0; i < 100; i++)
  17.                 {
  18.                     data.Move(Random.Shared.Next(data.Count), Random.Shared.Next(data.Count));
  19.                 }
  20.             }
  21.             return data;
  22.         });
  23.         ListView.ItemsSource = newList;
  24.     }
复制代码
3、第三个方法是自己实现一个类似 ObservableCollection 的类型。例子就是开篇的Foolist类,但是缺点就是在通知 UI 线程集合变更之后,刚好 UI 线程去读取此集合新的值的时候,集合本身就被其他线程更改了内容,那么此时的逻辑就不是符合预期的。
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

惊雷无声

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表