处理大量小任务并保持UI响应

时间:2013-11-06 17:21:42

标签: wpf system.reactive dispatcher reactiveui

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

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

我订阅了Dispatcher.Hooks.OperationStarted事件,看看在我的任务处理过程中是否发生了任何渲染作业,但他们没有。

任何想法发生了什么?

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

{{1}}

在这个例子中,incomingTasks会连续产生3000多条消息,ObserveOn将每次调用SomeMethodWhichDoesWork推送到Dispatcher队列,以便稍后处理

3 个答案:

答案 0 :(得分:5)

基本问题

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

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

关于观察DispatcherScheduler

的警告 默认情况下,

ObserveOn(DispatcherScheduler)会将所有内容推送到普通优先级。更新版本的Rx在重载时允许您指定优先级。

有一点要强调的是,经常错过的是DispatcherScheduler一旦到达 NOT 就会将项目排队到Dispatcher上。

因此,如果您的3000个项目都相当接近,您将在Dispatcher上备份3000个正常优先级的操作,阻止相同或较低优先级的所有内容,直到完成为止 - 包括渲染操作。这几乎可以肯定你所看到的 - 这意味着你可能仍然会看到问题,即使你在后台线程上进行UI更新工作,取决于你的UI更新有多重。

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

推荐

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

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

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

更大的图片

值得稍微退一步看一下大局。

如果您将3000个项目连续分别转储到用户界面,那么该怎么做?他们最多将运行一台刷新率为100Hz的显示器,可能更低。我发现每秒10帧的帧速率对于大多数用途来说已经足够了。

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

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

我也会考虑前后的总体方法。如果您有理由确定要立即更新那么多项目,为什么不缓冲它们并以块的形式管理它们?这可能会一直有利于源 - 这可能是在服务器的某个地方?在任何情况下,Rx都有一些不错的运算符,比如Buffer可以将单个项的流转换为更大的列表 - 并且它具有可以按时间缓冲大小的重载。 / p>

答案 1 :(得分:0)

您是否尝试使用.SubscribeOn(TaskPoolScheduler.TaskPool)订阅其他主题?

答案 2 :(得分:0)

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

将您的工作放在不同的优先级队列上,不会让它同时运行,只是异步运行。

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