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

Art*_*bin 1 wpf dispatcher system.reactive reactiveui

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

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

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

有什么想法发生了什么?

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

IObservable<TaskMessage> incomingTasks;
incomingTasks.ObserveOn(RxApp.MainThreadScheduler).Subscribe(SomeMethodWhichDoesWork);
Run Code Online (Sandbox Code Playgroud)

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

Jam*_*rld 5

基本问题

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

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

关于观察DispatcherScheduler的警告

ObserveOn(DispatcherScheduler)默认情况下会以普通优先级推送所有内容.更新版本的Rx在重载时允许您指定优先级.

需要强调的一点是,经常错过的是DispatcherScheduler一旦到达就不会一个接一个地将项目排队到Dispatcher上.

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

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

建议

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

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

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

更大的图景

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

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

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

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

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