在WPF应用程序中,ObservableCollection由LINQ to SQL查询填充和更新。然后使用此ObservableCollection中的值更新UI对象。
通过LINQ to SQL查询更新此ObservableCollection的操作是否可以合理地在单独的线程中执行?
如果是,在这种情况下,它将成为此ObservableCollection的同一个实例吗? (我的意思是,如果从LINQ datacontext获取值并且为更新UI提供值的那个不同,那么我将无法更新UI)
答案 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
对此解决方案进行了更深入的讨论答案 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展示了唯一对我有用的东西:
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);
希望这对其他人有帮助。