【问题标题】:How to detect unresponsive UI updates?如何检测无响应的 UI 更新?
【发布时间】:2012-08-13 04:16:51
【问题描述】:

假设我有一个 WPF 应用程序,其中我有以下架构:

[工作线程] -> [队列1] -> [队列管理器线程] -> [队列2] -> [UI线程]

Worker 正在侦听来自某个服务的数据,并以不确定的时间(可能每秒多次或每隔几秒几次)接收该数据,然后将其排队到队列 1,然后是队列管理器,具体取决于“ UI 线程的健康”可能会决定提高/降低数据项排队到队列 2 的速率,UI 线程使用队列 2 来更新 UI,可能会丢弃一些项目,以免在事件它接收到太多消息(例如,它可能决定它将检查每个数据消息的时间戳,并且仅当消息之间的差异比 UI 更新的最后一个数据项早 5 秒时才将其排入队列 2。 )

UI 线程将有一个计时器,该计时器触发一个设置间隔,以使用队列 2 中的新数据更新 UI。我想要做的是确定 UI 更新自身的速度以测量它的“响应”,以限制例如增加/减少关于多久更新一次 UI 的计时器间隔

假设我的 UI 有很多控件(网格、图表等)都绑定到我的 UI shell 中队列 2 上不同的过滤/分组数据子集,并且在更新这些控件时 UI 开始变得无响应并在更新之间冻结,如何我可以从代码中检测到这一点,以便知道如何/何时增加/减少 UI 更新的间隔吗?基本上,我如何衡量跨绑定到数据的所有控件重新绑定整个 UI 需要多长时间?

顺便说一句,这是一个好的设计还是可以改进?还有其他我可以考虑的策略吗?

【问题讨论】:

标签: c# .net wpf multithreading


【解决方案1】:

有 2 个线程……主 UI 线程……和渲染线程……你可能需要同时查看这两个线程以确定应用程序的响应能力……并决定如何节流。

测量您的帧率

您可以观看 ETW 事件,或自己处理 CompositionTarget.Rendering 并计算帧数……这是监视渲染线程……如果这是丢帧,那么它可能会告诉您您的系统负担过重……并且您可以相应地限制您的后台工作。

使用计时器安排工作

您可以使用@HenkHolterman 提到的 DispatcherTimer 来监视 UI 线程上的负载,方法是让它以低于 Normal 的优先级运行,例如背景......当在 UI 线程上处理/处理计时器事件时......然后您可以释放/告诉您的后台工作人员执行下一项工作。

但是要小心...如果您的计时器间隔太小...并且您的系统超载...那么您可能会积累计时器消息...所以当您的事件处理程序被调用时...你可能会做太多的工作(除非你记录了上次调用它的时间)。

在 UI 线程上执行委托

或者,您可以让一个委托在 UI 线程上以特定优先级(例如通常为背景)运行。

如果您使用 Dispatcher.BeginInvoke....,则工作将被放入队列中,并在所有更高优先级的工作完成后执行。 (异步)。

如果您使用 Dispatcher.Invoke...,那么您的线程将阻塞,直到您正在调用的 Dispatcher 中所有更高优先级的工作都已完成,然后您的委托将执行。

(您的后台工作人员会在完成其工作单元后执行此操作,然后委托会告诉您的后台工作人员执行下一个工作单元)。

这可能比 DispatchTimer 更好,因为您可以消除一些延迟......即使用计时器的延迟取决于计时器间隔。

然后根据你的发现,你可以调整下一个工作单元。

监控各种系统性能计数器

如果您真的想变得更复杂,那么您可以从应用程序内部监控各种性能计数器,例如使用内存、GC 收集等...并动态限制您所做的工作量。

Dispatcher 的一些背景知识

【讨论】:

  • 非常有趣,谢谢!您能否详细说明一下 DispatcherTimer 在这里的作用,因为我不熟悉这个构造(另一位发帖人也在不同的上下文中提到了这一点,这两次都是我第一次遇到这个构造)跨度>
【解决方案2】:

我会简单地删除 QueueManager 和 Queue2。

您只需要一个 WPF Dispatcher Timer(而不是 Timers.Timer),它每隔几毫秒检查一次输入。您可以静态配置该超时。
然后使 Queue1 成为阻塞队列以限制上游的任何内容。

编辑

调度程序计时器可以使用certain priority 启动。选择一个较低的值,例如 Background 或 ContextIdle,您将永远不会超载 GUI。
然后只需确保计时器不会咬掉超过它可以咀嚼的东西。一次只有 1 个(或几个)项目。调整这部分,以便 GUI 将尽可能多地处理,但仅此而已。运行时调整是免费的,因为您与调度程序相关联。

并且(仅在需要时)您可以使用Queue1 = new BlockingCollection<MyItemType>(MaxItems),这样该队列就不会溢出。

【讨论】:

  • 嗯.. WPF 应用程序有“OnIdle”事件吗?
  • @MartinJames - 是的,有几种风格:msdn.microsoft.com/en-us/library/…
  • 当您说阻塞队列时,您是指 .NET 4.0 中的 ConcurrentQueue 实例吗?我对此不熟悉-如何使用它来帮助节流?另外,我不熟悉 DispatcherTimer 以及它如何适应它 - 我可以看到另一个滴答声在前一个滴答声触发之前不会触发的优势,但是这个计时器不是也是单线程的吗?如果更新需要很长时间,UI 会不会变得无响应,因为它现在基本上在一个线程上,可能还有其他滴答声排队?您能否进一步详细说明这些构造如何解决我的问题?
猜你喜欢
  • 2014-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多