ObservableCollection和List的区别和 ObservableCollection 异步调用问题

ObservableCollection和List的区别总结

区别它们,最简单的方法就是看看他们继承的类和接口,还有它们的方法。
(学习有技巧,会让你事半功倍,效率提升。)

  • ObservableCollection比较简单,继承了Collection, INotifyCollectionChanged, INotifyPropertyChanged

observable 能看得到的; 能察觉到的;值得注意的东西;感觉到[看得见]的事物;【物】可观察量;观察算符

观察者模式是软件设计模式的一种。在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实时事件处理系统。 — 维基百科

观察者模式又叫发布订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。

我们可以使用日常生活中,期刊订阅的例子来形象地解释一下上面的概念。期刊订阅包含两个主要的角色:期刊出版方和订阅者,他们之间的关系如下:

期刊出版方 - 负责期刊的出版和发行工作
订阅者 - 只需执行订阅操作,新版的期刊发布后,就会主动收到通知,如果取消订阅,以后就不会再收到通知
在这里插入图片描述

在观察者模式中也有两个主要角色:Subject (主题) 和 Observer (观察者)
它们分别对应例子中的期刊出版方和订阅者。接下来我们来看张图,从而加深对上面概念的理解。

  • Collection:为泛型集合提供基类。code
  • INotifyCollectionChanged:将集合的动态更改通知给侦听器,例如,什么时候添加和移除项或者重置整个集合对象。对象
  • INotifyPropertyChanged:向客户端发出某一属性值已更改的通知。排序

因此再ObservableCollection这个类的方法,对数据的操做不多,重点放在了当本身本事变化的时候(无论是属性,仍是集合)会调用发出通知的事件。(通常用于更新UI,固然也能够用于写其余的事情。这个之后会写)继承

List就比较多了,继承了IList, ICollection, IEnumerable, IList, ICollection, IEnumerable。索引

  • IList:表示可按照索引单独访问的一组对象:接口
  • ICollection:定义操做泛型集合的方法:事件
  • IEnumerable:公开枚举器,该枚举器支持在指定类型的集合上进行简单迭代:get
  • IList:表示可按照索引单独访问的对象的非泛型集合。
  • ICollection:定义全部非泛型集合的大小、枚举器和同步方法。
  • IEnumerable:公开枚举器,该枚举器支持在非泛型集合上进行简单迭代。

而后随便作个demo看看效果。

<ListBox x:Name="listbind" Height="61" HorizontalAlignment="Left" Margin="146,12,0,0" VerticalAlignment="Top" Width="120" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{
    
    Binding Name}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <ListBox x:Name="observbind" Height="74" HorizontalAlignment="Left" Margin="146,111,0,0" VerticalAlignment="Top" Width="120" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{
    
    Binding Name}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <TextBlock Height="23" HorizontalAlignment="Left" Margin="38,58,0,0" Name="textBlock1" Text="List绑定数据" VerticalAlignment="Top" />
        <TextBlock Height="44" HorizontalAlignment="Left" Margin="12,125,0,0" Name="textBlock2" Text="ObservableCollection绑定数据" VerticalAlignment="Top" Width="112" />
        <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="77,211,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />

xaml页面很简单,托2个listbox分别用来绑定ObservableCollection和List

public class Person
    {
    
    
        public string Name {
    
     get; set; }
    }

实体类。

private List<Person> person1 = new List<Person>();
        private ObservableCollection<Person> person2 = new ObservableCollection<Person>();

        public DemoTestDiff()
        {
    
    
            InitializeComponent();
            person1.Add(new Person() {
    
     Name = "张三" });
            person1.Add(new Person() {
    
     Name = "李四" });
            listbind.ItemsSource = person1;
            person2.Add(new Person() {
    
     Name = "张三" });
            person2.Add(new Person() {
    
     Name = "李四" });
            observbind.ItemsSource = person2;
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
    
    
            person1.Add(new Person() {
    
     Name = "王五" });
            person2.Add(new Person() {
    
     Name = "王五" });
        }

运行程序点击button按钮

而后只有ObservableCollection的有添加。表示当集合对象的集合改变时,只有ObservableCollection会发出通知更新UI。 这只是他们两个区别之一。

综上所述:

ObservableCollection表示一个动态数据集合,在添加项、移除项或刷新整个列表时,此集合将提供通知。
List表示可经过索引访问的对象的强类型列表。提供用于对列表进行搜索、排序和操做的方法。(大部分操做用Linq,很强大也很方便。)

问题介绍

当ObservableCollection列表被UI线程占用时,若是在异步线程中调用ObservableCollection,会弹出如下异常:

问题分析
咱们使用一个viewModel,在ViewModel中添加ObservableCollection类型的ItemsSource列表。
异步在列表使用ListBox绑定ItemsSource列表。再由界面触发对ItemsSource的修改。

 1     public class ViewModel : INotifyPropertyChanged
 2     {
    
    
 3         private ObservableCollection<string> _itemsSource = new ObservableCollection<string>();
 4 
 5         public ObservableCollection<string> ItemsSource
 6         {
    
    
 7             get => _itemsSource;
 8             set
 9             {
    
    
10                 _itemsSource = value;
11                 OnPropertyChanged();
12             }
13         }
14 
15         public event PropertyChangedEventHandler PropertyChanged;
16 
17         [NotifyPropertyChangedInvocator]
18         protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
19         {
    
    
20             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
21         }
22     }

View Code

  1. 直接在异步线程下修改ObservableCollection–报错this
  2. private void ButtonBase_OnClick(object sender, RoutedEventArgs e) 2 { 3 var viewModel = this.DataContext as ViewModel; 4 Task.Run(() => 5 { 6 //此段调用异常 7 viewModel.ItemsSource.Add("test1"); 8 }); 9 }
  3. 在异步线程下,赋值ObservableCollection–正常spa
 private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
 2     {
    
    
 3         var viewModel = this.DataContext as ViewModel;
 4         Task.Run(() =>
 5         {
    
    
 6             //此段不会报错
 7             var list = viewModel.ItemsSource.ToList();
 8             list.Add("test0");
 9             viewModel.ItemsSource = new ObservableCollection<string>(list);
10         });
11     }
  1. 在异步线程下,赋值ObservableCollection后,再修改ObservableCollection–正常
5. `  private void Button1_OnClick(object sender, RoutedEventArgs e)
 2     {
    
    
 3         var viewModel = this.DataContext as ViewModel;
 4         Task.Run(() =>
 5         {
    
    
 6             //此段不会报错
 7             viewModel.ItemsSource = new ObservableCollection<string>(new List<string>() {
    
     "test3", "test2" });
 8             //此段不会报错
 9             viewModel.ItemsSource.Add("test4");
10         });
11     }`

在异步线程下设置的ItemsSource,能够被UI线程调用。此处能够理解为,赋值时,框架默默转到UI线程处理了?get

可是上面3流程,为什么正常,so weird~string

  1. 异步线程下,回到UI线程中,修改ObservableCollection–正常
6.  private void Button1_OnClick(object sender, RoutedEventArgs e)
 2     {
    
    
 3         var viewModel = this.DataContext as ViewModel;
 4         Task.Run(() =>
 5         {
    
    
 6             Application.Current.Dispatcher.Invoke(() =>
 7             {
    
    
 8                 //此段不会报错
 9                 viewModel.ItemsSource.Add("test");
10             });
11         });
12     }

在进行WPF开发过程当中,须要从一个新的线程中操做ObservableCollection,结果程序抛出一个NotSupportedException的错误ide

public class AsyncObservableCollection<T> : ObservableCollection<T>
{
    
    
    //获取当前线程的SynchronizationContext对象
    private SynchronizationContext _synchronizationContext = SynchronizationContext.Current;
    public AsyncObservableCollection() {
    
     }
    public AsyncObservableCollection(IEnumerable<T> list) : base(list) {
    
     }
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
    
    
          
        if (SynchronizationContext.Current == _synchronizationContext)
        {
    
    
            //若是操做发生在同一个线程中,不须要进行跨线程执行         
            RaiseCollectionChanged(e);
        }
        else
        {
    
    
            //若是不是发生在同一个线程中
            //准确说来,这里是在一个非UI线程中,须要进行UI的更新所进行的操做         
            _synchronizationContext.Post(RaiseCollectionChanged, e);
        }
    }
    private void RaiseCollectionChanged(object param)
    {
    
    
        // 执行         
        base.OnCollectionChanged((NotifyCollectionChangedEventArgs)param);
    }
    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
    
    
        if (SynchronizationContext.Current == _synchronizationContext)
        {
    
    
            // Execute the PropertyChanged event on the current thread             
            RaisePropertyChanged(e);
        }
        else
        {
    
    
            // Post the PropertyChanged event on the creator thread             
            _synchronizationContext.Post(RaisePropertyChanged, e);
        }
    }
    private void RaisePropertyChanged(object param)
    {
    
    
        // We are in the creator thread, call the base implementation directly         
        base.OnPropertyChanged((PropertyChangedEventArgs)param);
    }
}

观察者模式实战

Subject 类定义:

class Subject {
    
    
    
    constructor() {
    
    
        this.observerCollection = [];
    }
    
    registerObserver(observer) {
    
    
        this.observerCollection.push(observer);
    }
    
    unregisterObserver(observer) {
    
    
        let index = this.observerCollection.indexOf(observer);
        if(index >= 0) this.observerCollection.splice(index, 1);
    }
    
    notifyObservers() {
    
    
        this.observerCollection.forEach((observer)=>observer.notify());
    }
}

Observer 类定义:

class Observer {
    
    
    
    constructor(name) {
    
    
        this.name = name;
    }
    
    notify() {
    
    
        console.log(`${
    
    this.name} has been notified.`);
    }
}

使用示例:

let subject = new Subject(); // 创建主题对象

let observer1 = new Observer('semlinker'); // 创建观察者A - 'semlinker'
let observer2 = new Observer('lolo'); // 创建观察者B - 'lolo'

subject.registerObserver(observer1); // 注册观察者A
subject.registerObserver(observer2); // 注册观察者B
 
subject.notifyObservers(); // 通知观察者

subject.unregisterObserver(observer1); // 移除观察者A

subject.notifyObservers(); // 验证是否成功移除

Pull vs Push

Pull 和 Push 是数据生产者和数据的消费者两种不同的交流方式。

什么是Pull?
在 “拉” 体系中,数据的消费者决定何时从数据生产者那里获取数据,而生产者自身并不会意识到什么时候数据将会被发送给消费者。

每一个 JavaScript 函数都是一个 "拉" 体系,函数是数据的生产者,
调用函数的代码通过 ''拉出" 一个单一的返回值来消费该数据。
const add = (a, b) => a + b;
let sum = add(3, 4);

什么是Push?
在 “推” 体系中,数据的生产者决定何时发送数据给消费者,消费者不会在接收数据之前意识到它将要接收这个数据。

Promise(承诺) 是当今 JS 中最常见的 “推” 体系,一个Promise (数据的生产者)发送一个 resolved value (成功状态的值)来执行一个回调(数据消费者),但是不同于函数的地方的是:Promise 决定着何时数据才被推送至这个回调函数。

RxJS 引入了 Observables (可观察对象),一个全新的 “推” 体系。一个可观察对象是一个产生多值的生产者,当产生新数据的时候,会主动 “推送给” Observer (观察者)。
生产者 消费者
pull拉 被请求的时候产生数据 决定何时请求数据
push推 按自己的节奏生产数据 对接收的数据进行处理
接下来我们来看张图,从而加深对上面概念的理解:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/kalvin_y_liu/article/details/117825389
今日推荐