【问题标题】:Updating an ObservableCollection in a separate thread在单独的线程中更新 ObservableCollection
【发布时间】:2011-01-07 10:58:59
【问题描述】:

在 WPF 应用程序中,ObservableCollection 由 LINQ to SQL 查询填充和更新。然后使用此 ObservableCollection 中的值更新 UI 对象。

LINQ to SQL 查询更新此 ObservableCollection 的操作是否可以在单独的线程中执行?

如果是,在这种情况下,它会是这个 ObservableCollection 的同一个实例吗? (我的意思是,如果从 LINQ 数据上下文中获取值和为更新 UI 提供值的不是同一个,那么我将无法更新 UI)

【问题讨论】:

    标签: c# .net wpf multithreading linq-to-sql


    【解决方案1】:

    我没有收到相同的System.NotSupportedException,但不幸的是,如果我从另一个线程设置我的ObservableCollectiony<MyType>,我的UI 没有正确更新

    唯一对我有用的是https://www.codeproject.com/Tips/1111432/Update-data-to-WPF-control-from-another-thread

    在视图模型中

    private SynchronizationContext _syncContext = SynchronizationContext.Current;
    
    private ObservableCollection<PackageModel> _packageModelList;
    public ObservableCollection<PackageModel> PackageModelList
    {
         get => _packageModelList;
         set
         {
             if (_packageModelList == value)
                 return;
             packageModelList = value;
             RaisePropertyChanged("PackageModelList");
         }
    }      
    

    在其他线程中

     _syncContext.Send(x => { PackageModelList = OtherThreadPackageModels; },null);
    

    希望这对其他人有所帮助。

    【讨论】:

      【解决方案2】:

      .Net 4.5 在 BindingOperations 类中提供了一个解决方案。

      您现在可以使用 BindingOperations.EnableCollectionSynchronization 方法,如下所示:

      private readonly object _personCollectionLock;
      private ObservableCollection<Person> _personCollection;
      
      public ObservableCollection<Person> PersonCollection
      {
        get { return _personCollection; }
        set
        { 
          _personCollection = value;
          BindingOperations.EnableCollectionSynchronization(_personCollection, _personCollectionLock);
        }
      

      我只是在我的开发环境中尝试过这个,但是当我从后台线程更新集合时,现在一切似乎都正常工作了。

      对此解决方案有更深入的讨论:http://10rem.net/blog/2012/01/16/wpf-45-observable-collection-cross-thread-change-notification

      此方法的 MSDN 条目位于:https://msdn.microsoft.com/en-us/library/system.windows.data.bindingoperations.enablecollectionsynchronization(v=vs.110).aspx

      【讨论】:

        【解决方案3】:

        使用内置 ObservableCollection&lt;T&gt; 类,如果 UI 绑定到集合,则无法从单独的线程更改内容,它会引发 NotSupportedException(但集合项属性的更改通知工作正常)。我写了一个AsyncObservableCollection&lt;T&gt; class 来处理这个案子。它通过在 UI 同步上下文中调用事件处理程序来工作

        【讨论】:

        【解决方案4】:

        在我们的应用程序中,我们有一个绑定到 ObservableCollection 的 TreeView,我们会定期在后台线程中更新它,从我们的存储中请求数据。效果很好!

        哎呀。我被误导了=))

        没错,我们实际上是继承ObservableCollection&lt;T&gt; 并覆盖OnCollectionChanged 方法以避免UI 跨线程异常。我们正在使用this solution

        public class MTObservableCollection<T> : ObservableCollection<T>
        {
           public override event NotifyCollectionChangedEventHandler CollectionChanged;
           protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
           {
              var eh = CollectionChanged;
              if (eh != null)
              {
                 Dispatcher dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()
                         let dpo = nh.Target as DispatcherObject
                         where dpo != null
                         select dpo.Dispatcher).FirstOrDefault();
        
                if (dispatcher != null && dispatcher.CheckAccess() == false)
                {
                   dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => OnCollectionChanged(e)));
                }
                else
                {
                   foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList())
                      nh.Invoke(this, e);
                }
             }
          }
        }
        

        如果没有那个覆盖,你会得到一个这样的异常

        System.NotSupportedException:这个 CollectionView 的类型不 支持对其进行更改 来自线程的 SourceCollection 不同于 Dispatcher 线程。

        现在我们唯一的问题是选定项的位置,在某些情况下,如果从集合中删除当前选定的项,TreeView 会将选定项移动到下一个项(这会导致我们的应用程序中出现一些其他不必要的 UI 操作)。但这是一个小问题。

        【讨论】:

        • 这怎么可能?您确定要编组回 UI 线程以应用数据存储中的更改吗?否则,你会得到一个例外......
        • 您必须创建 ObservableCollection 的子类,将 CollectionChanged 事件编组回 UI 线程。我发现这比让 ViewModel 管理更新更容易管理。
        • 谢谢,肯特,奈杰尔——你说得对!我已经更正了我的答案,这是我的错误。
        【解决方案5】:

        在这里尝试理解您的问题:

        场景 1 1. LINQ to SQL 从数据库中检索数据集并添加到 ObservableCollection A。 2. 定期从数据库中检索更多数据并添加到 A。旧数据从 A 中删除。 场景 2 1. LINQ to SQL 从数据库中检索数据集并添加到 ObservableCollection A。 2. 定期用数据库中的新数据更新 A 中的数据(不添加/删除)。

        对于场景 1,您将不得不使用 UI 线程。 UI 线程拥有 ObservableCollection,如果您尝试在另一个线程中使用它,您将收到异常。

        对于场景 2,竖起大拇指。只要您不尝试从集合本身添加或删除项目,您就可以在后台线程中尽可能多地更新项目。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-07-05
          • 1970-01-01
          • 1970-01-01
          • 2012-07-08
          • 1970-01-01
          • 2017-01-04
          相关资源
          最近更新 更多