2

我有一个 WPF 应用程序需要对许多小任务进行一些处理。这些小任务都是同时生成的,并以 Normal 的优先级添加到 Dispatcher Queue 中。同时显示忙碌指示符。结果是,尽管工作被分解为任务,但忙碌指示器实际上会冻结。

我尝试将这些任务的优先级更改为后台,看看是否修复了它,但繁忙的指示器仍然冻结。

我订阅了该Dispatcher.Hooks.OperationStarted事件以查看在处理我的任务时是否发生了任何渲染作业,但它们没有。

有什么想法吗?

一些技术细节:任务实际上只是来自Observable序列的消息,它们通过调用 ReactiveUI 被“排队”到调度程序中,ObserveOn(RxApp.MainThreadScheduler)这应该相当于ObserveOn(DispatcherScheduler). 每个任务的工作部分是通过 ObserveOn 调用订阅的代码,例如

IObservable<TaskMessage> incomingTasks;
incomingTasks.ObserveOn(RxApp.MainThreadScheduler).Subscribe(SomeMethodWhichDoesWork);

在此示例中,incomingTasks 可能会在短时间内连续产生 3000 多条消息,ObserveOn 将对 SomeMethodWhichDoesWork 的每个调用推送到 Dispatcher 队列中,以便稍后处理

4

3 回答 3

6

基本问题

您看到繁忙指标停止的原因是因为您SomeMethodWhichDoesWork花费的时间太长。在它运行时,它会阻止 Dispatcher 上发生任何其他工作。

为处理动画而生成的 Input 和 Render 操作的优先级低于 Normal,但优先级高于 Background 操作。但是,Dispatcher 上的操作不会被更高优先级操作的入队中断。因此,即使是后台操作,渲染操作也必须等待正在运行的操作。

关于观察 DispatcherScheduler 的警告

ObserveOn(DispatcherScheduler)默认情况下,将以 Normal 优先级推送所有内容。较新版本的 Rx 具有允许您指定优先级的过载。

需要强调的一点是经常被忽略的一点是,一旦项目到达,DispatcherScheduler 就会将它们排队到 Dispatcher 上,而不是一个接一个地到达。

因此,如果您的 3000 个项目都非常接近,您将在 Dispatcher 上备份 3000 个正常优先级的操作,阻塞所有相同或更低优先级的操作,直到它们完成 - 包括渲染操作。这几乎可以肯定是您所看到的 - 这意味着即使您在后台线程上完成了 UI 更新之外的所有工作,您可能仍然会看到问题,具体取决于您的 UI 更新的繁重程度。

除此之外,您应该检查您没有在 UI 线程上运行整个订阅 - 正如 Lee 所说。我通常编写我的代码,以便我Subscribe在后台线程上而不是使用 SubscribeOn,尽管这也很好。

建议

无论你做什么,都尽可能多地在后台线程上工作。这一点在 StackOverflow 和其他地方已经被彻底完成了。以下是一些很好的资源:

如果您想在面对大量小更新时保持 UI 响应,您可以:

  • 以较低的优先级安排项目,这很好也很容易 - 但如果您需要某个优先级,那就不太好
  • 将更新存储在您自己的队列中并将它们排入队列并让您运行的每个操作在最后一步调用队列中的下一个项目。

更大的图景

值得退后一步,看看更大的图景。

如果你分别将 3000 个项目连续转储到 UI 中,这对用户有什么影响?充其量他们将运行刷新率为 100Hz 的显示器,可能更低。我发现每秒 10 的帧速率对于大多数用途来说已经足够了。

不仅如此,据说人类一次不能处理超过 5 到 9 位的信息——所以你可能会找到比一次更新这么多东西更好的方法来聚合和显示信息。例如,利用主/详细视图而不是一次在屏幕上显示所有内容等。

另一种选择是查看您的 UI 更新造成的工作量。一些控件(我在看你 XamDataGrid)可能有非常冗长的测量/排列布局操作。你能简化你的动画吗?使用更简单的可视化树?想想看起来像圆点的流行忙碌的微调器 - 但实际上它只是在改变它们的颜色。一个很好的效果,实现起来相当便宜。值得对您的应用程序进行概要分析,以了解时间的去向。

我也会考虑从前到后的整体方法。如果您有理由确定要一次更新这么多项目,为什么不缓冲它们并分块管理它们呢?这可能会一直追溯到源头——这可能在某处的服务器上?在任何情况下,Rx 都有一些不错的操作符,比如Buffer可以将单个项目的流转换为更大的列表 - 它具有可以按时间大小一起缓冲的重载。

于 2013-11-07T13:16:54.477 回答
0

您是否尝试过.SubscribeOn(TaskPoolScheduler.TaskPool)在不同的线程上订阅?

于 2013-11-06T21:54:05.840 回答
0

@Pedro Pombeiro 有正确的答案。您在 UI 上看到冻结的原因是您在 Dispatcher 上排队工作。这意味着工作将在 UI 线程上完成。您可以将 Dispatcher 视为一个消息泵,它不断从其每个队列中排出消息(您可以将其视为每个优先级 [SystemIdle、ApplicationIdle、ContextIdle、Background、Input、Loaded、Render、DataBind、Normal、Send ])

将您的工作放到不同的优先级队列上,不会使其同时运行,而只是异步运行。

要使用 Rx 在另一个线程上运行您的工作,请如上所述使用 SubscribeOn。请记住,然后使用 ObserveOn 将 UI 的任何更新安排回 Dispatcher。

于 2013-11-06T23:47:46.833 回答