在单独的线程中更新ObservableCollection

时间:2010-01-20 20:15:35

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

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

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

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

5 个答案:

答案 0 :(得分:26)

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

答案 1 :(得分:21)

.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

答案 2 :(得分:6)

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

糟糕。我被误导了=))

是的,我们实际上是对ObservableCollection<T>进行子类化并覆盖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操作)。但这是一个小问题。

答案 3 :(得分:2)

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

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).

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

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

答案 4 :(得分:1)

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

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

在ViewModel中

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

希望这对其他人有帮助。