【问题标题】:Howto keep Delphi App Responsive during GUI Updates?如何在 GUI 更新期间保持 Delphi 应用程序响应?
【发布时间】:2010-02-02 11:15:13
【问题描述】:

这个问题是关于在长时间运行的任务(大多数情况下是几秒钟)期间保持 GUI 响应。

我广泛使用线程和任务模式在后台线程中执行昂贵的任务。但是需要一些时间的 GUI 更新呢?例如,填充大的字符串网格或树视图?线程在这里没有帮助,因为无论如何都需要与主线程同步。

我知道Application.ProcessMessages 的问题,但目前它似乎是将调用ProcessMessages 放在GUI 更新方法中的唯一解决方案。

有更好的想法吗?

【问题讨论】:

  • 我认为“多线程”标签不适用于该问题。

标签: delphi user-interface


【解决方案1】:

IMO 如果 GUI 更新是瓶颈(即使 BeginUpdate/EndUpdate 像 @The_Fox 建议的那样使用),那么是时候重新考虑使用的 GUI 控件了。标准网格、树视图、列表框并不是为了处理大量项目而简单地切割的。为此目的,有许多免费和商业的高性能第三方控件。

首先,如果瓶颈在网格、树视图或列表框上下文中,请查看 VirtualTreeview。

【讨论】:

  • 将您的列表视图或树视图更改为 [VirtualTreeView][1]。在 Delphi 2009/2010 中,调试消息“列表视图”实际上是一个虚拟树视图,因为其他控件不断插入项目,更新速度太慢,无法满足用户的期望。你似乎也有类似的问题。您想要的是更快的 GUI 控件。 [1]:delphi-gems.com/…
【解决方案2】:

填充网格、列表、数据集等时,调用 BeginUpdate/EndUpdate DisableControls/EnableControls。这将节省您的时间。我也有一个线程进行一些计算,但是 GUI 很慢,直到我对正在修改且不可见的数据集调用 DisableControls,因为控件位于另一个选项卡上。

此外,在更新控件时,请准备好所有需要的数据,这样您就可以填写您的列表,而无需进行会减慢速度的计算。

【讨论】:

  • @Smasher:如果是这样,您可能对大型数据集使用了不合适的控件。使用例如虚拟树控件(或类似的虚拟控件)不应该有延迟。按需加载数据(例如扩展树节点时)也会有所帮助。另外,也许一次可见的数据太多,用户无论如何都无法处理?过滤可能是一种选择。
  • @mghi:我使用 TMS 字符串网格组件和默认的 treeView(我知道这很慢)。无论如何,我想避免(如果可能的话)切换到其他组件,因为很难保持相同的外观和感觉(我主要关心这里的字符串网格)。此外,还有过滤选项,但我不能强迫用户实际使用它们。
  • @Smasher:嗯,使用OnCollapsedOnExpanding 将有助于加快标准树视图的速度。
  • 标准的树视图很烂。更改为 VirtualTreeView(见下文)
  • 我可以在商业应用中使用虚拟树视图吗?
【解决方案3】:

在您的情况下 Application.ProcessMessages 有什么问题? Application.ProcessMessages 方法正是针对这种情况的。 Application.ProcessMessages 的问题是这样的代码:

repeat
  Application.ProcessMessages;
until SomethingHappens;

这很糟糕,因为它是无用的 CPU 负载,应该替换为

repeat
  Application.HandleMessage;
until SomethingHappens;

将处理器时间留给其他线程。单个 Application.ProcessMessages 调用(不在循环中)是可以的。

【讨论】:

  • 这不是唯一的问题。你也很容易遇到重入问题之类的问题。
  • 重入是应用逻辑的问题。您不能为此责备通用的消息处理方法。当然,当您在 GUI 线程中执行一些耗时的操作时,应该以不同的方式处理某些事件。
  • 我知道,我现在正在使用ProcessMessages。不知道是不是有什么问题……所以才问的。
  • 好吧,错误的是在 GUI 线程中执行耗时的操作。理论上,总是可以避免的。在实践中,并非总是如此。 Application.ProcessMessages 就是针对这种情况的。
  • @Serg:重入问题来自这样一个事实,即每次调用这些方法时,您都为应用程序提供了一个机会,以一种可能出乎意料的方式响应用户。因此,除非您随后添加额外的代码来禁用用户输入源,以防万一用户“点击风暴”,否则使用这些方法只是为了保持 UI 响应(在这种情况下,通常情况下,这似乎是关于“及时更新”,而不是“响应用户”)是自找麻烦。
【解决方案4】:

看看ActiveSleep

【讨论】:

    【解决方案5】:

    如果您只想处理绘制消息而不是其他:使用以下内容而不是 Application.ProcessMessages:

    procedure ProcessPaintMessages;
    var
      Msg: TMsg;
      i: Integer;
    begin
      i := 0;
      repeat
        if Windows.PeekMessage(Msg, 0, 0, 0, PM_REMOVE or (QS_PAINT shl 16)) then begin
          TranslateMessage(Msg);
          DispatchMessage(Msg);
        end else Break;
        Inc(i);
      until i > 1000; // Breakout if we are in a paint only loop!
    end;
    

    【讨论】:

    • 有趣。但是循环条件从何而来?如果每次我调用该函数都必须执行 1000 次重绘,那将是相当昂贵的......
    • 我在一个我每 200 毫秒调用一次的地方使用它,因此处理 1000 条消息似乎并不过分。您可以将最大循环计数作为参数。请注意,通常此函数不处理或处理 1 条消息。条件只是为了避免油漆消息“失控”。
    【解决方案6】:

    您试用过 Indy 的 AntiFreeze 组件吗?

    【讨论】:

    • 他指的是填充 GUI 组件时出现的问题,而不是由于 Indy TCP/IP 框架的阻塞特性而冻结。
    • 您认为 AntiFreeze 在内部做什么?
    猜你喜欢
    • 1970-01-01
    • 2010-09-30
    • 2010-09-14
    • 1970-01-01
    • 2014-12-03
    • 1970-01-01
    • 2012-03-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多