【问题标题】:Why does the UI thread hang when updating list in background?为什么在后台更新列表时 UI 线程会挂起?
【发布时间】:2014-12-10 06:31:40
【问题描述】:

我认为我对线程的处理非常好,直到我遇到了一个我想对不同网格的更新进行基准测试的场景。

我创建了一个带有网格控件的窗口,将ObservableCollection 绑定到它,并用5000 行某些复杂数据类型(不包含锁)填充它。

然后我使用

创建了一个任务
 Task.Factory.StartNew()

这经历了一个非常紧凑的循环(1000 万次迭代),更新了我的 ObservableCollection 中随机项目的随机属性,当然每个都会引发 INotifyPropertyChanged 事件。

现在,由于更新都发生在后台线程上,我希望 UI 能够更新,尽管很难跟上这个在紧密循环中旋转的后台线程。

相反,UI 冻结了几秒钟(但没有变成空白或产生通常的厄运旋转光标),然后在后台线程完成后返回。

我的理解是,后台线程会在产生大量 INPC 的同时对内核造成相当大的负担,每个 INPC 都会由 WPF 运行时自动编组到 UI 线程。

现在 UI 线程什么也不做,所以我希望它消耗所有这些 INPC 并更新网格,但它没有;没有发生一次更新。但是,当我使用计时器(而不是紧密循环)执行此操作时,它可以正常工作。

有人能告诉我 UI 线程到底在做什么吗?提前致谢!

【问题讨论】:

  • 你能提供最短的代码示例来说明这个问题吗?
  • “紧密循环”是指每次迭代都没有延迟吗?这似乎是一个简单的 UI 被无法跟上的更新轰炸的案例。

标签: c# wpf multithreading


【解决方案1】:

如果你用大量这样的分派更新阻塞了消息泵,其他消息将没有机会被处理,这会导致你观察到的“冻结”效果。

这里可以提供帮助的一件事是在您的 UI 控件上使用数据虚拟化,以便实际绑定可见行并侦听 INPC 更新。 DataGrid 默认启用此功能,但如果您使用更自定义的方法来可视化数据,这可能是个问题。

也就是说,这无助于频繁修改当前可见的项目,因为真正快速的更新仍然会阻塞调度程序。如果您有这样的用例,您可能希望稍微隔离您的视图模型对象并有一种方法来“批量”更新。一种技术是在进行大量更新时抑制通知,然后在每个实例上调用 RaisePropertyChanged(null)(或 INPC 帮助器基类上的任何等效方法)以更新与该实例的所有绑定。

另一种机制是在其他层中进行数据更新(无论您的视图模型实例代表什么模型对象),然后以明确定义的间隔将这些属性复制到视图模型类。为了快速更新后台数据,我经常使用轮询循环而不是触发事件,原因很简单,因为事件会比 UI 关心的更频繁地发生,并且会减慢后台处理以不断发送所有这些不必要的通知。

【讨论】:

  • 谢谢科比;您(或知情人士)会详细说明消息泵在此期间正在做什么吗?为什么调度员会“堵塞”?例如,假设我是 UI 线程,而您是工作人员,并且您生成更新的速度比我处理它们的速度要快。我们仍然作为完全独立的线程并行工作,所以对于你生成的每 100 个更新,我应该能够处理其中的 10 个,不是吗?我当然会落后,但我应该还在工作,不会显得闲着。
  • @Anthony,有点复杂,但我的理解是数据绑定有一个比较高的DispatcherPriority,这意味着调度的通知队列堆积起来并在大量其他消息之前得到服务否则希望被处理。因此,该应用程序并没有真正陷入僵局,但它实际上给您的响应能力可能有限。
  • 特别是,'Input' 和 'Render' 消息的优先级都低于 'DataBinding',这意味着应用程序对输入没有响应并且不会更新其呈现的输出,但实际的 Windows 处理程序仍在处理(和排队),所以窗口仍然显示,只是它最后缓存的渲染输出。
  • 谢谢丹,这很有意义;我完全忘记了数据绑定的优先级......你会碰巧有一个链接到一篇详细描述这个的文章吗?更新:没关系,我有一秒钟的精神障碍。我现在一切都清楚了,非常有道理,谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-19
  • 2015-03-20
相关资源
最近更新 更多