草庐IT

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

log9527blog 2023-03-28 原文

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  中引用 )

public class FooList<T> : Collection<T>, INotifyCollectionChanged
{
    protected override void InsertItem(int index, T item)
    {
        base.InsertItem(index, item);

        Application.Current.Dispatcher.InvokeAsync(() =>
        {
            CollectionChanged?.Invoke(this,
          new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
        });
    }

    protected override void RemoveItem(int index)
    {
        var item = this[index];

        base.RemoveItem(index);

        Application.Current.Dispatcher.InvokeAsync(() =>
        {
            CollectionChanged?.Invoke(this,
          new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
        });
    }

    protected override void SetItem(int index, T item)
    {
        var oldItem = this[index];
        base.SetItem(index, item);
        Application.Current.Dispatcher.InvokeAsync(() =>
        {
            CollectionChanged?.Invoke(this,
          new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, oldItem, index));
        });
    }

    public event NotifyCollectionChangedEventHandler? CollectionChanged;
}

从上面的例子可以看出,ObservableCollection集合是不需要再进行属性通知的,所以下面的写法是不需要的

        public ObservableCollection<string> ObservableCollection
        {
            set
            {
                _observableCollection = value;
                OnPropertyChanged();
            }
            get
            {
                return _observableCollection;
            }
        }

        private ObservableCollection<string> _observableCollection;

在跨线程处理 ObservableCollection 数据,大多数时候都会抛出 System.NotSupportedException:“该类型的 CollectionView 不支持从调度程序线程以外的线程对其 SourceCollection 进行的更改。” 等异常。原因是ObservableCollection 是非线程安全的,因此所以在单一的时刻,只能有一个线程进行处理。如果 ObservableCollection 被 UI 元素捕获,例如加入到 ItemsSource 里面,那么此时的 ObservableCollection 不仅只能被单一线程处理,还要求这个线程是 UI 线程。(如果是给ObservableCollection赋值或者查询,则不会抛异常,如果是对ObservableCollection 进行增删改操作则可能会抛异常)针对以上情况,采取3中办法处理。

1、现在后台线程处理完ObservableCollection,再赋值给 ListView 的 ItemsSource 属性,实现更新界面逻辑。

private async void Button1_Click(object sender, RoutedEventArgs e)
    {
        var list = await Task.Run(() =>
        {
            ObservableCollection<string> data = new ObservableCollection<string>();
            for (int i = 0; i < 100; i++)
            {
                data.Add(Random.Shared.Next(1000).ToString());
            }
            return data;
        });

        // 以上代码使用 await 等待,可以自动切回主线程

        ListView.ItemsSource = list;
    }

2、在确保 UI 线程不会改动到 ObservableCollection 列表的时候,可以采用如下方法,在后台线程拷贝一份作为新的 ObservableCollection 对象,然后对此新的对象进行处理。完成之后,再将新的 ObservableCollection 对象赋值给到 UI 进行绑定。

private async void Button2_Click(object sender, RoutedEventArgs e)
    {
        // 假定 ListView.ItemsSource 存在源了
        if (ListView.ItemsSource is not ObservableCollection<string> list)
        {
            // 如果假设失败,强行给一个源
            list = new();
            ListView.ItemsSource = list;
        }

        var newList = await Task.Run(() =>
        {
            var data = new ObservableCollection<string>(list);

            // 模拟对原有的列表进行处理
            if (data.Count > 0)
            {
                for (int i = 0; i < 100; i++)
                {
                    data.Move(Random.Shared.Next(data.Count), Random.Shared.Next(data.Count));
                }
            }

            return data;
        });

        ListView.ItemsSource = newList;
    }

3、第三个方法是自己实现一个类似 ObservableCollection 的类型。例子就是开篇的Foolist类,但是缺点就是在通知 UI 线程集合变更之后,刚好 UI 线程去读取此集合新的值的时候,集合本身就被其他线程更改了内容,那么此时的逻辑就不是符合预期的。

 4、ViewModel的实例化和DataContext赋值不要使用异步,避免UI线程在绑定ObservableCollection给控件数据源时,ViewModel的实例化又对ObservableCollection进行操作。

有关深入了解WPF中ObservableCollection及跨线程处理ObservableCollection的更多相关文章

  1. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  2. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  3. ruby - 如何让Ruby捕获线程中的语法错误 - 2

    我正在尝试使用ruby​​编写一个双线程客户端,一个线程从套接字读取数据并将其打印出来,另一个线程读取本地数据并将其发送到远程服务器。我发现的问题是Ruby似乎无法捕获线程内的错误,这是一个示例:#!/usr/bin/rubyThread.new{loop{$stdout.puts"hi"abc.putsefsleep1}}loop{sleep1}显然,如果我在线程外键入abc.putsef,代码将永远不会运行,因为Ruby将报告“undefinedvariableabc”。但是,如果它在一个线程内,则没有错误报告。我的问题是,如何让Ruby捕获这样的错误?或者至少,报告线程中的错误?

  4. ruby - 如何在 ruby​​ 中运行后台线程? - 2

    我是ruby​​的新手,我认为重新构建一个我用C#编写的简单聊天程序是个好主意。我正在使用Ruby2.0.0MRI(Matz的Ruby实现)。问题是我想在服务器运行时为简单的服务器命令提供I/O。这是从示例中获取的服务器。我添加了使用gets()获取输入的命令方法。我希望此方法在后台作为线程运行,但该线程正在阻塞另一个线程。require'socket'#Getsocketsfromstdlibserver=TCPServer.open(2000)#Sockettolistenonport2000defcommandsx=1whilex==1exitProgram=gets.chomp

  5. ruby - Rails 开发服务器、PDFKit 和多线程 - 2

    我有一个使用PDFKit呈现网页的pdf版本的Rails应用程序。我使用Thin作为开发服务器。问题是当我处于开发模式时。当我使用“bundleexecrailss”启动我的服务器并尝试呈现任何PDF时,整个过程会陷入僵局,因为当您呈现PDF时,会向服务器请求一些额外的资源,如图像和css,看起来只有一个线程.如何配置Rails开发服务器以运行多个工作线程?非常感谢。 最佳答案 我找到的最简单的解决方案是unicorn.geminstallunicorn创建一个unicorn.conf:worker_processes3然后使用它:

  6. Ruby-vips 图像处理库。有什么好的使用示例吗? - 2

    我对图像处理完全陌生。我对JPEG内部是什么以及它是如何工作一无所知。我想知道,是否可以在某处找到执行以下简单操作的ruby​​代码:打开jpeg文件。遍历每个像素并将其颜色设置为fx绿色。将结果写入另一个文件。我对如何使用ruby​​-vips库实现这一点特别感兴趣https://github.com/ender672/ruby-vips我的目标-学习如何使用ruby​​-vips执行基本的图像处理操作(Gamma校正、亮度、色调……)任何指向比“helloworld”更复杂的工作示例的链接——比如ruby​​-vips的github页面上的链接,我们将不胜感激!如果有ruby​​-

  7. ruby - Faye WebSocket,关闭处理程序被触发后重新连接到套接字 - 2

    我有一个super简单的脚本,它几乎包含了FayeWebSocketGitHub页面上用于处理关闭连接的内容:ws=Faye::WebSocket::Client.new(url,nil,:headers=>headers)ws.on:opendo|event|p[:open]#sendpingcommand#sendtestcommand#ws.send({command:'test'}.to_json)endws.on:messagedo|event|#hereistheentrypointfordatacomingfromtheserver.pJSON.parse(event.d

  8. ruby - 了解在 Ruby 中与 lambda 一起使用的 inject 行为 - 2

    我经常将预配置的lambda插入可枚举的方法中,例如“map”、“select”等。但是“注入(inject)”的行为似乎有所不同。例如与mult4=lambda{|item|item*4}然后(5..10).map&mult4给我[20,24,28,32,36,40]但是,如果我制作一个2参数lambda用于像这样的注入(inject),multL=lambda{|product,n|product*n}我想说(5..10).inject(2)&multL因为“inject”有一个可选的单个初始值参数,但这给了我......irb(main):027:0>(5..10).inject

  9. ruby - 如何使用 Ruby HTTP::Net 处理 404 错误? - 2

    我正在尝试解析网页,但有时会收到404错误。这是我用来获取网页的代码:result=Net::HTTP::getURI.parse(URI.escape(url))如何测试result是否为404错误代码? 最佳答案 像这样重写你的代码:uri=URI.parse(url)result=Net::HTTP.start(uri.host,uri.port){|http|http.get(uri.path)}putsresult.codeputsresult.body这将打印状态码和正文。

  10. ruby-on-rails - 如何测试自己对 Ruby/ROR 的了解? - 2

    是否有self验证的问题列表。看着那个,我可以确定我知道。我应该复习一下。在学习的过程中,我列了一个这样的list,但它只包含我在某处听说过的项目。我需要一段时间才能找到新的东西。 最佳答案 以下是针对ruby​​和Rails的一些测试列表。证书名称:RubyonRails谁提供:oDeskIncorporation认证费用:免费网站:https://www.odesk.com/tests/985?pos=0证书名称:RubyonRails提供者:Techgig.com(TimesBusinessSolutionsLimited(T

随机推荐