我正在研究一个WPF应用程序,发现属性更改了绑定属性的通知可能发生在后台线程中,但是对于observablecollection(如添加或删除项目)的任何更改都必须从UI线程发生。我的问题是为什么会这样? INotifyPropertyChanged和INotifyCollectionChanged都是由UI控件订阅的,那么为什么INotifyPropertyChanged会出现异常?
例如:
public class ViewModel : INotifyPropertyChanged
{
ObservableCollection<Item> _items = new ObservableCollection<Item>();
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
//Can fire this from a background thread without any crash and my
//Name gets updated in the UI
InvokePropertyChanged(new PropertyChangedEventArgs("Name"));
}
}
public void Add(Item item)
{
//Cant do this from a background thread and has to marshal.
_items.Add(item);
}
public event PropertyChangedEventHandler PropertyChanged;
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
}
注意:来自后台线程的CollectionChanged事件会使应用程序崩溃,但后台线程中的PropertyChanged事件会更新UI而不会出现任何问题,是的,这是在.NET 4.0中
答案 0 :(得分:5)
此问题与线程安全无关。问题是CollectionChanged
事件是从工作线程引发的,这意味着处理程序在同一个线程中执行,当处理程序试图触摸UI时,你有一个例外,因为只允许从UI线程
如果情况相同,PropertyChanged
事件也会发生同样的情况,对这两种情况都没有特别的处理。
如果您需要从事件处理程序中触摸UI,则必须确保the event is raised on the UI thread或者如果需要对UI线程进行编组更改,则事件处理程序必须与Dispatcher.CheckAccess
进行检查并Dispatcher.BeginInvoke
这样做。
答案 1 :(得分:0)
您很可能是指绑定机制。
绑定不能直接与 CLR 属性和 PropertyChanged 事件一起使用。
绑定使用反射来完成它的工作。
它们按优先级顺序排列:PropertyDescriptor、PropertyInfo、DependencyProperty 和 DynamicPropertyAccessor。
你可以在这里看到它:https://referencesource.microsoft.com/PresentationFramework/R/ab9ce65d9e2b75ab.html
出于同样的原因,如果属性仅通过绑定更改,则无论 INotifyPropertyChanged 接口是否存在,它都会受到监视。
另外,反射的使用让你不用担心在哪个流中观察到的属性会发生变化。
在集合的情况下,绑定也不受流的影响,该集合将在其中分配给受监控的属性。
但是对集合变化的监控(INotifyPropertyChanged 和 IBindingList)不再由绑定机制提供,而是由 ItemsControl 类的内部逻辑提供。
已经有一个对观察事件的直接订阅,这使得这个逻辑对集合将发生变化的线程敏感。
如果不是UI线程,那么观察就会被销毁,甚至会抛出异常。
只要相信这一点就足够了。
演示示例。 具有随机集合更改的类 ViewModel。
public class RandomCollectionViewModel
{
public ObservableCollection<int> Numbers { get; }
= new ObservableCollection<int>() { 1, 2, 3 };
private static readonly Random random = new Random();
private readonly Timer timer = new Timer();
public RandomCollectionViewModel()
{
timer.Interval = 500;
timer.Elapsed += (s, e)
=> Numbers[random.Next(Numbers.Count)] = random.Next();
timer.Start();
}
}
XAML 使用绑定来显示集合项:
<StackPanel>
<FrameworkElement.DataContext>
<local:RandomCollectionViewModel/>
</FrameworkElement.DataContext>
<TextBlock Text="{Binding Numbers[0]}"/>
<TextBlock Text="{Binding Numbers[1]}"/>
<TextBlock Text="{Binding Numbers[2]}"/>
</StackPanel>
XAML 使用 ItemsControl
显示集合项:
<StackPanel>
<FrameworkElement.DataContext>
<local:RandomCollectionViewModel/>
</FrameworkElement.DataContext>
<ItemsControl ItemsSource="{Binding Numbers}"/>
</StackPanel>
这些示例清楚地显示了绑定和 ItemsContol
的工作差异。