WPF - 从非 UI 线程更新绑定 属性
WPF - Update bound property from non-UI thread
我无法理解是否必须使用 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
来手动编组集合的修改,以便能够 post关键操作返回到正确的上下文(线程)。
// 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);
});
由于您的第二个示例与第一个示例相同,因此出于同样的原因它不会抛出。您没有修改集合,例如Add/Insert/Move/Remove,但此集合中包含的项目:
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
。其他线程必须使用关联的 Dispatcher
或 DispatcherObject
所有者线程的 SynchronizationContext
。
如果您想将 DispatcherObject
从一个线程传递到另一个线程,例如,将 Brush
传递给 UI 线程,那么只有当 DispatcherObject
来自 Freezable
。您会调用 Freezable.Freeze
,它会提升线程亲和力,即 Dispatcher
亲和力,并允许将实例传递给其他线程。
我无法理解是否必须使用 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
来手动编组集合的修改,以便能够 post关键操作返回到正确的上下文(线程)。
// 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);
});
由于您的第二个示例与第一个示例相同,因此出于同样的原因它不会抛出。您没有修改集合,例如Add/Insert/Move/Remove,但此集合中包含的项目:
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
。其他线程必须使用关联的 Dispatcher
或 DispatcherObject
所有者线程的 SynchronizationContext
。
如果您想将 DispatcherObject
从一个线程传递到另一个线程,例如,将 Brush
传递给 UI 线程,那么只有当 DispatcherObject
来自 Freezable
。您会调用 Freezable.Freeze
,它会提升线程亲和力,即 Dispatcher
亲和力,并允许将实例传递给其他线程。