【问题标题】:Is it safe to bind WPF controls to data updated in Timer?将 WPF 控件绑定到 Timer 中更新的数据是否安全?
【发布时间】:2016-12-15 03:49:25
【问题描述】:

我有一个 WPF 应用程序,它在一个以 1 Hz 更新其数据的计时器内更新其数据模型。据我了解,计时器在与 GUI 不同的线程上工作。一切似乎都很好,但我正在阅读并看到关于是否可以在不同于 GUI 线程的线程上更新数据的相互矛盾的报告。我们正在使用 .NET 框架 4 客户端配置文件运行。下面的文章说事情在 4.5 中已修复,但我仍然不清楚它是。有人可以为我澄清一下吗?到目前为止,我们还没有在绑定中使用集合。这就是我们没有遇到麻烦的原因。

WPF Databinding thread safety?

【问题讨论】:

  • 使用 DispatcherTimer。它的 Tick 事件在 UI 线程中引发。

标签: c# wpf data-binding thread-safety


【解决方案1】:

是的。它是线程安全的。 INotifyPropertyChanged 总是从其他线程编组到 UI 线程。

无需将 PropertyChanged 从另一个线程分派到 UI 线程,因为 PropertyChanged 事件会自动编组到 UI 调度程序。

正如a MSDN article 所说:

请注意,在 WPF 中,情况有所不同,代码如图 5 所示 即使 Status 属性数据绑定到 TextBlock 也可以工作。这个 是因为 WPF 自动将 PropertyChanged 事件调度到 主线程,与所有其他 XAML 框架不同。在所有其他 框架,需要一个调度解决方案。

但是,它仅适用于标量属性的更改通知(即PropertyChanged 事件)。集合更改通知(INotifyCollectionChanged.CollectionChanged 事件)不能那样工作,它们必须在 UI 线程上手动引发。也就是说,当使用INotifyCollectionChanged(例如使用ObservableCollection)时,这些更改不会封送至UI 线程。这意味着如果您从非 UI 线程修改集合,您将接受异常。例如,有一些 ViewModel 我们在 ViewModel 类中,我们不使用 Dispatcher 来更新 UI。所以我建议你使用 David Rickard 的方法:

public static class DispatchService
{
    public static void Invoke(Action action)
    {
        Dispatcher dispatchObject = Application.Current.Dispatcher;
        if (dispatchObject == null || dispatchObject.CheckAccess())
    {
            action();
        }
        else
        {
            dispatchObject.Invoke(action);
        }
    }
}

和:

DispatchService.Invoke(() =>
{
    this.MyCollection.Add("new value");
});

David Rickard article at msdn blog.

更新:

是的,article 使用 MVVMLight 框架。但是,MVVM Light 使用Dispatcher 将标量属性编组到 UI 线程是不正确的。从MVVM LightViewModelBase类的源代码可以看出there is no marshal between threads to update scalar property。请参阅RaisePropertuChanged() 方法。

为了消除对调度标量属性的任何疑问,我做了一个测试:

XAML:

<TextBlock Text="{Binding Number}" FontSize="188" Foreground="Red" />

视图模型:

public int Number { get; set; }

private void UpdateNumber()
{
        Task.Run(() => 
        {
            System.Timers.Timer timer = new System.Timers.Timer(250);
            timer.Elapsed += (sender, eventArgs) =>
            { 
                Number++;
                OnPropertyChanged("Number");//No exceptions, no errors
            };
            timer.Enabled = true;
        });
}

更新 1:

毫无疑问,INotifyProperyChanged 事件由 WPF 自动分派到 UI 线程。我相信a MSDN article 和您在问题中显示的link :)。

注意这一点:这是因为 WPF 会自动将 PropertyChanged 事件分派到主线程,这与所有其他 XAML 框架不同。

【讨论】:

  • 请注意,本文使用的是切换到 UI 线程的 MVVM Light 框架。所以这个答案对于所有 WPF 应用程序都不正确
  • 非常有趣。谢谢。
  • @ErnodeWeerd,你能澄清一下什么对 WPF 不起作用。我们没有使用 MVVM Light 框架。
  • @bsh152s - 所引用的代码使用 MVVM Light 框架Set(StatusPropertyName, ref _status, value); 另外,我从未见过任何代码证明 WPF 在引发 NotifyPropertyChanged 事件时会自动切换到 UI 线程。我很乐意对此进行更正,但这篇文章并不能证明这一点。
  • 似乎有很多关于此的错误信息。我相信这里的关键是“INotifyProperyChanged 事件由 WPF 自动分派到 UI 线程”。因此,框架为您完成了困难的工作。
【解决方案2】:

不,它不是线程安全的。解决此问题的常用方法是在通知属性更改事件的引发程序中切换到 UI 线程。

每秒更新视图或视图模型 1000 次通常是没有用的,因为显示每秒仅更新 60 次左右,而普通用户无法每秒读取一千个文本值。

引发通知事件的相同代码可能会限制每秒引发事件的次数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-12
    • 2010-10-13
    • 2011-10-07
    • 1970-01-01
    相关资源
    最近更新 更多