来自不同线程的WPF和NotifyPropertyChanged

4

我在WPF方面有很丰富的经验,但有一件事情一直困扰着我。我正在使用ReactiveUI来触发INotifyPropertyChanged事件。我有两个类似于这样的地方:

 public UiModel UiModel
            {
                get { return _uiModel; }
                set { this.RaiseAndSetIfChanged(ref _uiModel, value); }
            }

    public void Run()
    {
      UiModel = GetUIModel();
    }

在这个代码片段中,Run() 被这样调用:await Task.Run(()=>Run());,且 UiModelXAML 中被绑定。那么基本上应该会出现跨线程异常,如果在不同的线程上调用 Run() 函数,对吗?我在代码中有两个地方,其中一个总是抛出异常,而另一个地方则从未抛出过异常。我已经比较了线程 ID,在两个地方 Run() 都不是在 UI 线程上运行。为什么会这样呢?

在没有抛出异常的地方,我将其绑定到 UserControl 的 Dependency Property 上,在第二个地方,我使用普通的绑定进行绑定。

抛出的异常信息:{"The calling thread cannot access this object because a different thread owns it."}

看起来我已经在这个特定的情况下找到了答案。异常并不是由 UiModel 和它的绑定引起的,而是由 ReactiveUI 验证中使用了 UiModel 导致的。如果我不从 UI 更新它,它将尝试从不同的线程更新 CanExecuteChanged,从而导致异常。然而我的问题仍然存在——为什么我可以在不同的线程上更新 UI,却没有出现跨线程异常?我在 ReactiveUI PropertyChanged 调用中没有看到 Dispatcher 调用。


只有在尝试从与UI线程不同的线程修改UI元素时,才会出现异常。这是否发生在第二个地方? - LoekD
我在两个地方都进行了修改。当将值分配给UiModel时,我调用INotifyPropertyChanged,这样就会更新UI。 - MistyK
1个回答

4
在这个领域有一些令人讨厌的不一致性。以下是您可以在工作线程上执行和不能执行的操作列表。
引发跨线程异常:
- 修改控件上的 DependencyProperty - 引发 ICommand.CanExecuteChanged 事件 - 引发 INotifyCollectionChanged.CollectionChanged 事件,通常由 ObservableCollection 实例引发
不会抛出异常:
- 引发 INotifyPropertyChanged.PropertyChanged 事件,通常从视图模型绑定引发
我相信 WPF 之所以不自动调用第一个列表,肯定有充分的理由,但我想不出来是什么。
我处理服务事件的建议如下:
1. 保留对从 UI 线程捕获的 SynchronizationContext 的引用(可能在这里最有用)。 2. 使用中介器服务在 UI 线程上调用应用程序事件处理程序。

1
谢谢,伙计,这很有道理!这个有文档记录吗? - MistyK
1
值得注意的是,自动封送只由一些MVVM框架完成。WPF和Xamarin都有,但WP没有,至于UAP我不确定。 - Stephen Cleary

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接