WPF双向绑定和从后台线程更新UI

时间:2014-06-27 19:52:32

标签: wpf multithreading asynchronous task backgroundworker

我担心我可能已经知道了这个问题的答案,但我正在抓住最小的希望,我错了。

我的存储库包含带有项列表的Collection属性。此集合是通过Web服务调用更新的,该调用执行以下两项操作之一。如果是第一次调用,它将为返回的每一行创建一个Item的新实例,填充它,并将其添加到Collection。后续调用将在集合中查找该项并更新其属性。 ViewModel,Item Class和Repository实现了INotifyPropertyChanged。

对dataservice的调用每隔30秒执行一次使用async / await更新数据,其任务类似于:How to execute a method periodically from WPF client application using threading or timer。 ViewModel从repositorys Collection中获取项目并将它们拆分为各种集合属性。然后,View通过ItemsControl绑定到每个集合中的各个项目,并且属性会不断更新。

一切都运行良好......但是,DataService调用不在自己的线程中,尽管async / await,UI每隔30秒就会有一点反应迟钝。当我把DataService调用放在BackgroundWorker中时,我意识到当它抛出一个关于从另一个线程修改的异常时我不能。

我对WinForms中的这个问题非常熟悉,但我希望以某种方式回避WPF和twoway绑定。有没有办法通过async / await使UI更具响应性,或者是否有办法将更新放在线程中而不必编写事件来支持调度程序在主线程上进行更新?

2 个答案:

答案 0 :(得分:1)

您的问题可能在于单独通知每个项目的每个属性。使用WPFPref(我相信仍然是Windows SDK的一部分),您可以看到UI的脏区域(正在渲染的区域)。如果您发现整个UI一次又一次地渲染会导致性能下降。

对此的解决方案可能是阻止在您进行刷新时通知UI。有几种选择:

  1. 根本不使用INotifyPropertyChanged,但在viewmodel上公开LoadCompleted事件,并让您的视图订阅它。然后手动刷新DataGrid上的View。我个人不喜欢这个解决方案,因为它有点反MVV​​M,但效率很高。
  2. 不要单独通知每个属性,但是当您完成一行时,请调用OnPropertyChanged并将null作为属性名称。这将通知该行的所有内容。可以通过向视图模型添加布尔_deferRefresh来临时禁用更新的正常属性,并在引发事件之前让OnPropertyChanged()检查此属性。
  3. 不要单独通知每个项目,但禁用#2中的通知,而是在集合中引发PropertyChanged。
  4. 如果您唯一的问题是渲染,请尝试在行上设置固定高度,并在单元格上固定。这将确保单元格中的新值不会再次渲染整个网格。

答案 1 :(得分:0)

在绑定中使用" Binding.IsAsync" 属性。当绑定源属性的get访问器可能需要很长时间时,将IsAsync属性设置为true。当IsAsync属性为true时,在绑定值之前,UI不会被阻止。有关详细信息:MSDN

一个示例是具有从Web下载的get访问器的图像属性。将IsAsync设置为true可避免在下载时阻止UI。

您还可以使用PriorityBinding来满足您的要求。 Windows Presentation Foundation(WPF)中的PriorityBinding通过指定绑定列表来工作。绑定列表按从最高优先级到最低优先级排序。如果最高优先级绑定在处理时成功返回值,则永远不需要处理列表中的其他绑定。可能是最高优先级绑定需要很长时间才能被评估,成功返回值的下一个最高优先级将被使用,直到更高优先级的绑定成功返回值。

<PriorityBinding FallbackValue="defaultvalue">
        <Binding Path="SlowestDP" IsAsync="True"/>
        <Binding Path="SlowerDP" IsAsync="True"/>
        <Binding Path="FastDP" />
</PriorityBinding>

详细信息:PriorityBinding