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

问题描述 投票:-1回答:1

我无法理解是否必须使用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也是一个好习惯:似乎是一种出于信仰的行为...
c# wpf multithreading data-binding dispatcher
1个回答
0
投票

您的第一个示例未修改任何与DispatcherObject相关的UI线程。绑定引擎将自动封送INotifyPropertyChanged.PropertChanged事件或将其注册的事件处理程序调用到UI线程。这表示Binding.Target(即DispatcherObject)始终在UI线程上正确更新。

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

这不适用于INotifyCollectionChanged.CollectionChanged。从后台线程更改的集合必须通过调用为该线程注册的Dispatcher或捕获拥有或调用线程的SynchronizationContext.Current,然后将关键操作回传给它,来调度对集合的修改。

// Will throw a cross-thread exception
MyBoundPropertyCollection.Add(item); 

// Safe
Dispatcher.Invoke(() => MyBoundPropertyCollection.Add(item)); 

// Safe
capturedSynchronizationContext.Post(state => MyBoundPropertyCollection.Add(item), null); 

由于您的第二个示例与第一个示例相同,因此不会出于完全相同的原因抛出该错误。您没有修改集合,例如添加/插入/移动/删除,但此集合中包含一个项目:

MyBoundPropertyCollection[0] += DateTime.Now.Second;

等于

var myBoundProperty = MyBoundPropertyCollection[0];

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

每个从DispatcherObject派生的对象,例如TextBox通过将其映射到线程Dispatcher与在其上创建的线程关联(线程相似性)。仅允许该线程修改DispatcherObject。其他线程已使用关联的Dispatcher所有者线程的SynchronizationContextDispatcherObject

[如果要将DispatcherObject从一个线程传递到另一个线程,例如,将Brush传递到UI线程,则仅当DispatcherObjectFreezable派生时才可能。您将调用Freezable.Freeze,这将提升线程关联性,即Dispatcher关联性,并允许将实例传递给其他线程。

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.