UI调度缓冲

本文关键字:缓冲 调度 UI | 更新日期: 2023-09-27 17:53:28

我有一个难题。我"继承"了一个设计非常糟糕、非常复杂的系统,我正在(和我的团队)一点一点地对它进行现代化和重建。问题是,当前的系统由200多个用户所依赖,并且由于(缺乏)设计,他们在性能方面存在重大问题。目前最有问题的问题是,大量的工作被放在UI线程上,这导致GUI挂起,直到线程被清除,消息泵送可以继续。这些工作中的大部分实际上需要在GUI线程上进行,因为由于其他线程上的其他计算结果,它正在更新网格中的大量字段。

问题是这样的:我没有足够的资源来重写这里涉及的线程模型和底层类,而且这项工作的复杂性会带来巨大的风险,这是我的客户无法接受的。

我想知道是否有人有任何关于如何使UI更高性能的建议,而不会过多地干扰当前的线程模型

我最初的想法是,可能有一些方法可以在实际调用UI线程之前放置一个"缓冲区",以确保GUI不会过载,或者当GUI过载时,可以退出对它的调度。

任何建议都将非常感谢。

我知道这些都不理想,但我们就是这样,我真的想在长达一年的重写完成之前给我的用户一个更好的体验!

谢谢!

Update # 1 这是一个winforms应用程序…对不起,我一开始就没说清楚。新的代码是WPF,但这些模块是winforms。

更新# 2 我想我可能首先尝试将大多数对UI线程的BeginInvoke调用更改为Invoke,引入序列化,这有望提高UI的响应性。有谁能预见到(不明显的)缺点吗?

UI调度缓冲

我不知道它是否适用于您的特殊情况,但我过去遇到过类似的情况(尽管可能没有那么大的压力),我想出了一些代码,让我从后台线程编组或多或少的任意代码到UI线程。这是在WinForms时代写的

这可能为你提供了一种低风险的技术,你可以把一些计算扔回一些后台线程,更容易地把你的UI更新送到前台,而不需要完全重新构建线程模型(或缺乏)。

关于UI的性能,有时使它更快的最好方法是做得更少。当我创建链接代码时,我正在开发的应用程序正在解析数百兆字节的字符串,以手动修改一些xml。用stringbuilder替换不可变字符串使操作在5分钟内从OOM故障状态变为完成。然后我注意到,对于它解析的每个元素,它都会更新UI。我将代码调整为每50个元素左右更新一次ui,这将时间缩短到不到一分钟。关闭所有UI更新将时间降低到几秒钟。

你有没有考虑过在计算完成之前不更新UI,或者只是运行一个进度条?另一种可能性(我没有想到,不知道这有多邪恶)是将更新作为lambdas在字典中排队,该字典与要更新的控件相关联。这样,如果一个控件被多次更新,你就可以替换对值的冗余更新,例如(当然,这假设应用程序不是直接从UI读取值来执行计算。很可能是这样。

如果你正在做大量的更新,你可以暂停和恢复绑定在你的控件,使用CurrencyManager的SuspendBinding和ResumeBinding方法。这样,如果由于大量UI交互而无法在单独的线程中移动某些内容,您仍然可以通过不显示更新来获得一些性能增益。另外,在更新结束前暂停任何列表更改通知可能会有所帮助,特别是在使用网格或其他控件时。

假设这是一个WinForms应用程序,一个相当hackish的方法是在UI线程上运行的密集工作中定期调用application . doevents()。这允许消息泵在您的逻辑中处理挂起的消息。请注意,这充满了它自己的一组危险,例如:

  1. 所有的UI事件可以触发,这意味着你有像按钮按触发长时间运行的UI更新,随后阻塞你以前的UI工作产生DoEvents()。一个特别阴险的形式是当你"双击"一个按钮时,它会旋转一个长操作,它产生给DoEvents(),它处理另一个按钮点击并旋转相同的操作,直到完成,然后在操作中间将控制返回给第一个按钮点击处理程序。这意味着在UI线程上的这些"长时间运行"操作期间,你必须非常小心你允许的UI交互。

  2. 因为用户可以与UI交互,所以他们可能会使你的代码之前所做的关于事情不变的假设无效。同样,限制用户可以做的事情是很重要的,这样他们就不会使您的操作所依赖的状态无效。当然,在另一个线程中运行代码也会遇到同样的问题。

  3. 这在VB6时代是一种相当普遍的技术(当时多线程很难实现),但在现代开发中却不受欢迎。它可能会让您摆脱遗留应用程序的困境,但我建议使用//HACK标记来标记它,以便将来清理。