WPF-从非UI线程更新绑定属性



我不明白是否必须使用Dispatcher来通知UI线程绑定属性已由非UI线程更新
从一个基本的例子开始,为什么下面的代码没有抛出著名的(我记得在过去的编码经验中曾在这个问题上挣扎过(调用线程无法访问这个对象,因为另一个线程拥有它异常?

private int _myBoundProperty;
public int MyBoundProperty { get { return _myBoundProperty; } set { _myBoundProperty = value; OnPropertyChanged(); } }
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void Test()
{
new System.Threading.Thread(() =>
{
try
{
MyBoundProperty += DateTime.Now.Second;
}
catch(Exception ex)
{
}
}).Start();
}
<TextBlock Text="{Binding MyBoundProperty"/>

我看到了一些关于这个话题的帖子,但在我看来,它们相当令人困惑:

  • 有人说Dispatcher是集合所必需的,但不是单个变量所必需的:那么为什么这个其他例子仍然没有抛出异常呢
private ObservableCollection<int> _myBoundPropertyCollection;
public ObservableCollection<int> MyBoundPropertyCollection { get { return _myBoundPropertyCollection; } set { _myBoundPropertyCollection = value; OnPropertyChanged(); } }
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void TestCollection()
{
new System.Threading.Thread(() =>
{
try
{
//MyBoundPropertyCollection = new ObservableCollection() { 0 }; //even so not throwing
MyBoundPropertyCollection[0] += DateTime.Now.Second;
}
catch(Exception ex)
{
}
}).Start();
}
<TextBlock Text="{Binding MyBoundPropertyCollection[0]"/>
  • 有人说这是以前的.NET版本所需要的:那么现在我们可以删除那些Dispatcher调用了吗(我使用的是.NET Framework 4.7(
  • 有人说使用Dispatcher是一种很好的做法,即使您的代码没有抛出异常:这似乎是一种信仰行为

您的第一个示例不是从禁止的线程修改任何与UI线程相关的DispatcherObject。绑定引擎自动将INotifyPropertyChanged.PropertChanged事件或对其注册的事件处理程序的调用封送至UI线程。这意味着UI的Binding.Target,即DispatcherObject,总是在UI线程上正确更新。

// Safe, because PropertyChanged will always be raised on the UI thread
MyBoundProperty = DateTime.Now; 

这不适用于INotifyCollectionChanged.CollectionChanged。从后台线程更改的集合必须手动封送对集合的修改,方法是调用为该线程注册的Dispatcher,或者捕获拥有或调用线程的SynchronizationContext.Current,以便能够将关键操作发布回正确的上下文(线程(。

// Assuming that this is the proper thread of the DispatcherObject, 
// that binds to MyBoundPropertyCollection
SynchronizationContext capturedSynchronizationContext = SynchronizationContext.Current;
Task.Run(() =>
{
// Will throw a cross-thread exception
MyBoundPropertyCollection.Add(item); 
// Safe
Dispatcher.Invoke(() => MyBoundPropertyCollection.Add(item)); 
// Safe
capturedSynchronizationContext.Post(state => MyBoundPropertyCollection.Add(item), null); 
});

由于您的第二个示例与第一个示例相同,因此它不会因为相同的原因而抛出。您不是在修改集合,例如添加/插入/移动/删除,而是在此集合中包含的项目:

Task.Run(() => MyBoundPropertyCollection[0] = DateTime.Now);

等于

Task.Run(() =>
{
var myBoundProperty = MyBoundPropertyCollection[0];
// Safe, because PropertyChanged will always be raised on the UI thread
myBoundProperty = DateTime.Now; 
});

DispatcherObject(例如TextBox(派生的每个对象都通过将其映射到线程的Dispatcher而与创建它的线程(线程亲和性(相关联。仅允许此线程修改DispatcherObject。其他线程必须使用相关联的DispatcherDispatcherObject所有者线程的SynchronizationContext

如果您想将DispatcherObject从一个线程传递到另一个线程,例如,将Brush传递到UI线程,那么只有当DispatcherObjectFreezable派生时,这才可能。您可以调用Freezable.Freeze,它提升线程亲和性,即Dispatcher亲和性,并允许将实例传递给其他线程。

最新更新