在单独的线程中更新ObservableCollection

时间:2022-06-19 21:02:04

In a WPF application an ObservableCollection is filled and updated by LINQ to SQL queries. Then UI objects are updated using values from this ObservableCollection.

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

Is it possible and reasonable that operations of updating this ObservableCollection by LINQ to SQL queries were executed in a separate thread?

通过LINQ to SQL查询更新此ObservableCollection的操作是否可能并且合理地在单独的线程中执行?

If yes, will, in this case, it be one and the same instance of this ObservableCollection? (I mean, if it is not the same one for taking values from LINQ datacontext and the one for giving values to update UI, then I will not be able to update UI)

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

4 个解决方案

#1


25  

With the built-in ObservableCollection<T> class, you can't change the content from a separate thread if the UI is bound to the collection, it throws a NotSupportedException (but change notification for properties of collection items works fine). I wrote an AsyncObservableCollection<T> class to handle this case. It works by invoking the event handlers on the UI synchronization context

使用内置的ObservableCollection 类,如果UI绑定到集合,则无法从单独的线程更改内容,它会抛出NotSupportedException(但集合项属性的更改通知正常工作)。我编写了一个AsyncObservableCollection 类来处理这种情况。它的工作原理是在UI同步上下文中调用事件处理程序

#2


18  

.Net 4.5 provides a solution within the BindingOperations class.

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

You can now use the BindingOperations.EnableCollectionSynchronization method as follows:

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

private readonly object _personCollectionLock;
private ObservableCollection<Person> _personCollection;

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

I have only just tried this in my development environment but everything seems to be working correctly now when I update the collection from a background thread.

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

There is a more in-depth discussion of this solution at: http://10rem.net/blog/2012/01/16/wpf-45-observable-collection-cross-thread-change-notification

有关该解决方案的更深入讨论,请访问:http://10rem.net/blog/2012/01/16/wpf-45-observable-collection-cross-thread-change-notification

The MSDN entry for this method is at: https://msdn.microsoft.com/en-us/library/system.windows.data.bindingoperations.enablecollectionsynchronization(v=vs.110).aspx

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

#3


6  

In our app, we have a TreeView bound to an ObservableCollection, which we regularly update in a background thread, requesting data from our storage. It works perfectly!

在我们的应用程序中,我们有一个绑定到ObservableCollection的TreeView,我们会定期在后台线程中更新,从我们的存储中请求数据。它完美地运作!

Whoops. I was mis-informed =))

哎呦。我被误导了=))

Right, we're actually subclassing the ObservableCollection<T> and override the OnCollectionChanged method to avoid the UI crossthreading exception. We're using this solution:

是的,我们实际上是对ObservableCollection 进行子类化并覆盖OnCollectionChanged方法以避免UI交叉线程异常。我们正在使用这个解决方案:

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);
        }
     }
  }
}

Without that override you'd get an exception like that

如果没有那个覆盖,你就会得到类似的异常

System.NotSupportedException : This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

System.NotSupportedException:这种类型的CollectionView不支持从与Dispatcher线程不同的线程更改其SourceCollection。

Now the only problem we have is the selected item position, in some cases if the currently selected item is deleted from the collection the TreeView moves the selection to the next item (which causes some other unnecessary UI actions in our app). But that's a small issue.

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

#4


2  

Trying to understand your question here:

试着在这里理解你的问题:

Scenario 1
1. LINQ to SQL retrieves data set from database and adds to ObservableCollection A.
2. Periodically, more data is retrieved from database and added to A. Old data is removed from A.

Scenario 2
1. LINQ to SQL retrieves data set from database and adds to ObservableCollection A.
2. Periodically, data in A is updated with new data from database (no add/remove).

With Scenario 1, you're going to have to use the UI thread. The UI thread owns the ObservableCollection and you'll get an exception if you try to use it in another thread.

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

With Scenario 2, thumbs up. As long as you don't try to add or remove items from the collection itself, you can update the item as much as you want in a background thread.

在场景2中,竖起大拇指。只要您不尝试在集合本身中添加或删除项目,就可以在后台线程中根据需要更新项目。

#1


25  

With the built-in ObservableCollection<T> class, you can't change the content from a separate thread if the UI is bound to the collection, it throws a NotSupportedException (but change notification for properties of collection items works fine). I wrote an AsyncObservableCollection<T> class to handle this case. It works by invoking the event handlers on the UI synchronization context

使用内置的ObservableCollection 类,如果UI绑定到集合,则无法从单独的线程更改内容,它会抛出NotSupportedException(但集合项属性的更改通知正常工作)。我编写了一个AsyncObservableCollection 类来处理这种情况。它的工作原理是在UI同步上下文中调用事件处理程序

#2


18  

.Net 4.5 provides a solution within the BindingOperations class.

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

You can now use the BindingOperations.EnableCollectionSynchronization method as follows:

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

private readonly object _personCollectionLock;
private ObservableCollection<Person> _personCollection;

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

I have only just tried this in my development environment but everything seems to be working correctly now when I update the collection from a background thread.

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

There is a more in-depth discussion of this solution at: http://10rem.net/blog/2012/01/16/wpf-45-observable-collection-cross-thread-change-notification

有关该解决方案的更深入讨论,请访问:http://10rem.net/blog/2012/01/16/wpf-45-observable-collection-cross-thread-change-notification

The MSDN entry for this method is at: https://msdn.microsoft.com/en-us/library/system.windows.data.bindingoperations.enablecollectionsynchronization(v=vs.110).aspx

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

#3


6  

In our app, we have a TreeView bound to an ObservableCollection, which we regularly update in a background thread, requesting data from our storage. It works perfectly!

在我们的应用程序中,我们有一个绑定到ObservableCollection的TreeView,我们会定期在后台线程中更新,从我们的存储中请求数据。它完美地运作!

Whoops. I was mis-informed =))

哎呦。我被误导了=))

Right, we're actually subclassing the ObservableCollection<T> and override the OnCollectionChanged method to avoid the UI crossthreading exception. We're using this solution:

是的,我们实际上是对ObservableCollection 进行子类化并覆盖OnCollectionChanged方法以避免UI交叉线程异常。我们正在使用这个解决方案:

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);
        }
     }
  }
}

Without that override you'd get an exception like that

如果没有那个覆盖,你就会得到类似的异常

System.NotSupportedException : This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

System.NotSupportedException:这种类型的CollectionView不支持从与Dispatcher线程不同的线程更改其SourceCollection。

Now the only problem we have is the selected item position, in some cases if the currently selected item is deleted from the collection the TreeView moves the selection to the next item (which causes some other unnecessary UI actions in our app). But that's a small issue.

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

#4


2  

Trying to understand your question here:

试着在这里理解你的问题:

Scenario 1
1. LINQ to SQL retrieves data set from database and adds to ObservableCollection A.
2. Periodically, more data is retrieved from database and added to A. Old data is removed from A.

Scenario 2
1. LINQ to SQL retrieves data set from database and adds to ObservableCollection A.
2. Periodically, data in A is updated with new data from database (no add/remove).

With Scenario 1, you're going to have to use the UI thread. The UI thread owns the ObservableCollection and you'll get an exception if you try to use it in another thread.

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

With Scenario 2, thumbs up. As long as you don't try to add or remove items from the collection itself, you can update the item as much as you want in a background thread.

在场景2中,竖起大拇指。只要您不尝试在集合本身中添加或删除项目,就可以在后台线程中根据需要更新项目。