UI调度缓冲?

4
我有一个难题。我继承了一个设计非常糟糕且非常复杂的系统,我正在与我的团队一步步地进行现代化和重建。问题是,目前的系统被200多个用户所依赖,并且由于(缺乏)设计而导致性能出现严重问题。目前最棘手的问题是,在UI线程上放置了大量的工作,这导致GUI挂起,直到线程被清除并消息泵可以继续。实际上,许多工作确实需要在GUI线程上完成,因为它正在更新网格中的大量字段,以响应其他线程上的计算结果。
问题是:我没有资源专门用于重新编写此处涉及的线程模型和基础类,并且该工作的复杂性会引入不可接受的风险,这对我的客户来说是无法接受的。
我想知道是否有人有任何建议,可以在不过多干扰当前线程模型的情况下使UI更加高效。
我的最初想法是,可能有一种方法可以在实际调用UI线程之前放置“缓冲区”,以确保GUI不会被过载,或者当GUI被过载时将其调度回来。
非常感谢任何建议。
我知道这一切都不理想,但我们已经到了这里,我真的希望在长达一年的重写完成之前为我的用户提供更好的体验!
谢谢!
更新#1 这是一个winforms应用程序……很抱歉一开始没有说明。新代码是WPF,但这些模块是winforms。
更新#2 我正在考虑最初将大多数BeginInvoke调用更改为Invoke,引入一种序列化,希望可以增加UI的响应能力。 是否有任何(非明显的)缺点,任何人都可以预见?

将异步调用变为同步调用实际上是回到单线程的方法。Invoke会阻塞调用线程直到UI线程更新,因此你不会获得任何好处。(当然,如果所有的invoke调用都来自UI线程,你也不会失去任何东西。) - Greg D
3个回答

1

我不知道它是否适用于你的特定情况,但我曾经遇到过类似的情况(虽然可能没有那么紧张),并且我想出了一些代码,让我能够从后台线程中调用更多或更少任意的代码,并将其转移到UI线程。这是在WinForms时代编写的。

这可能为您提供了一种低风险的技术,使您可以将一些计算抛回到某些后台线程中,并更轻松地将UI更新传输到前景,而无需完全重新架构线程模型(或缺乏线程模型)。

关于UI的性能,有时使其更快的最好方法是做得更少。当我创建链接代码时,我正在处理数百兆字节的字符串以手动操纵一些xml。将不可变字符串替换为stringbuilder将操作从OOM失败条件转换为5分钟的墙钟时间内完成。然后我注意到,对于它解析的每个元素,它都会更新UI。我调整了该代码,使其每50个元素更新一次UI,这将其降至几分之一分钟。关闭所有UI更新将时间缩短到几秒钟。

你有没有考虑在计算完成之前不更新UI,而是只运行一个进度条?另外一种可能性(我脑海中的想法,不知道有多邪恶)是将更新作为lambda函数排队存储在以被更新的控件为键的字典中。这样,如果单个控件被多次更新,就可以替换冗余的值更新,例如(当然,这假设应用程序不直接从UI读取值来执行计算。但它可能会。 :( )


好东西 Greg..这可能会给我一个缓冲区的良好起点! - miguel

0

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


我认为这些是基类上的方法:http://msdn.microsoft.com/zh-cn/library/system.windows.forms.bindingmanagerbase.aspx :) - Greg D

0

假设这是一个WinForms应用程序,一种相当hackish的方法是在UI线程上运行的密集工作中定期调用Application.DoEvents()。这允许消息泵在逻辑处理过程中处理挂起的消息。请注意,这本身就有一套危险,例如:

  1. 所有的UI事件都可以触发,这意味着你可能会有像按钮按下触发长时间运行的UI更新的情况,随后阻塞了之前使用DoEvents()产生的UI工作。这种情况特别阴险的是当你“双击”一个按钮时,它会启动一个长时间操作,然后让出控制权给DoEvents(),处理另一个按钮点击并启动相同的操作,直到完成,然后返回控制权给第一个按钮点击处理程序,在其操作中间。这意味着在UI线程上进行“长时间运行”的操作时,你必须非常小心地处理允许的UI交互。

  2. 因为用户可以与UI交互,所以他们可能会使您的代码先前对事物不变的假设无效。同样,限制用户可以做什么非常关键,以便他们不能使您的操作所依赖的状态无效。当然,如果在另一个线程中运行代码,也会遇到同样的问题。

  3. 这在VB6时代是一种相当常见的技术(在那个时候多线程非常难实现),但在现代开发中被认为是不好的做法。它可能会帮助您解决遗留应用程序中的问题,但我建议在未来进行清理时使用//HACK标记。


有趣...我喜欢它的简单性,也许在尝试更复杂的解决方案之前先尝试一下会很好(也许是修改格雷格的想法)...但我感觉交互会导致这个方案无法实现。可惜。 - miguel
1
这是一个危险的解决方案,因为你可以在几分钟内“修复”行为,但如果你想要它安全地工作,仍然有很多复杂性需要管理。你将会在其他更为复杂的解决方案中遇到相同的复杂性,但至少在这些情况下,复杂性更加明显。 - Dan Bryant
是的,Application.DoEvents() 是一项危险的业务。请注意,它可能会导致意外的重入,特别是在设计不良的系统场景下。 - Greg D

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接