【问题标题】:Show asynchronous 'wait' window显示异步“等待”窗口
【发布时间】:2019-08-13 11:03:58
【问题描述】:

在一个 Delphi VCL 应用程序中,我想创建一个“等待”消息窗口,因为这是一个耗时的过程(本例中是一个大的无用循环)。

在我开始耗时的过程之前,我尝试了以下要执行的事情。

-创建一个包含消息的简单窗口的新形式。

-使用 messagedlg 创建消息。

-甚至更改主窗体上的 TLabel.Caption(执行耗时过程的那个)。

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
  popUpMessage;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;

    procedure Button1Click(Sender: TObject);
  end;

var
  Form1: TForm1;
  dialog : TForm;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
    var i, j, k :LongInt;
begin


     {1}
     popUpMessage.Form2 := TForm2.Create(nil); //also tried with Create(self)
     Form2.show;

     {2}
     dialog := CreateMessageDialog ('Wait',TMsgDlgType.mtWarning, mbYesNoCancel);
     dialog.Show;


     {3}
     messagedlg('Wait',mtError, mbOKCancel, 0);

     {4}
      Label1.Caption := 'Wait';


     //Time consuming process
       for i := 0  to 200000 do
         for j := 0  to 20000do
           k := i-j;

   end;
end.

在 {1} 和 {2} 的情况下,弹出表单在耗时的过程开始之前出现,但它们的组件仅在此完成后才绘制。

案例 {3} 保持执行直到模式对话框关闭。

如果{4}在耗时的过程完成后字幕发生变化。

如何异步创建消息,使其完全绘制而不等待其父进程?

【问题讨论】:

  • 你必须把你的耗时过程放到一个单独的线程上。
  • @nolaspeaker ,这不就是把弹窗放到单独的线程里吗?
  • 由于您可能很快也会被ProcessMessages 的黑暗恶魔诱惑,这里有一些额外的相关阅读:I do not understand what Application.ProcessMessages in Delphi is doing
  • @Mikemik 您不能将弹出窗口放在单独的线程上。在 Delphi 中,UI(用户界面)线程是您的“显示”所在的位置。这就是为什么您必须为其他所有内容创建一个单独的线程。而且你永远不应该做任何在线程中“显示”的事情。请参阅下面 J... 的答案。

标签: delphi asynchronous non-modal


【解决方案1】:

这是一个有点宽泛的问题,所以我将提供一个简短的示例来演示如何将长时间运行的任务移至线程,并安全地向 UI 提供进度和完成反馈。

我不会为第二个进度表单而烦恼,对于这个示例,我只是将一个按钮 (Button1) 和一个进度条 (ProgressBar1) 添加到一个新的 TForm。

在下面的方法中,我们启动一个匿名线程来执行长时间运行的操作,并以定期的进度间隔定期通知主线程。我们还会在工作完成时通知。为了扩展这一点,您还可以执行错误检查/处理,还可以通知操作未能完成,但为了简洁起见,我将示例限制为简单的内容。

procedure TForm1.Button1Click(Sender: TObject);
begin
  ProgressBar1.Position := 0;
  Button1.Enabled := false;           
  Button1.Caption := 'Calculating...';
  TThread.CreateAnonymousThread(
    procedure
        procedure PerformProgressStep;
        begin
          TThread.Queue(nil, procedure
                             begin
                               ProgressBar1.StepIt;
                             end);
        end;
        procedure NotifyComplete;
        begin
          TThread.Queue(nil, procedure
                             begin
                               ShowMessage('done');
                               Button1.Enabled := true;
                               Button1.Caption := 'Start Task';
                             end);
        end;
    var
      i, j, k : integer;
    begin
      for i := 0  to 199999 do begin
        for j := 0  to 20000 do k := i-j;
        if (i mod 20000) = 0 then PerformProgressStep;
      end;
      NotifyComplete;
    end).Start;
end;

这里所有的 UI 操作都使用TThread.Queue 编组到主线程。 .Synchronize 也是一个选项,如果您必须等待主线程处理完工作才能继续线程中的操作,但您必须注意避免在这种情况下出现死锁情况。

此代码中没有任何错误处理,请注意 - 这只是为了演示如何将您的工作转移到后台线程中。还有许多其他方法可以做到这一点,使用匿名线程(如上),自定义 TThread(如果您有更重的实现要封装),使用 Tasks 和 RTL 线程池,使用 Threading 单元中的并行循环操作等. 您决定使用哪一种取决于您的特定应用程序的要求。


如需更深入地探索 Delphi 中的多线程,您可以随时关注Martin Harvey's excellent article here。这是一篇相当老的文章,因此它在较低级别上涉及多线程主题,并使用基本的 RTL 和 WinAPI 结构,并且不包含更现代版本的 Delphi 中包含的任何新语言功能。然而,它具有很强的指导意义,是复习基础知识的绝佳读物。

【讨论】:

  • 除了@J... 的出色回答,您还可以看看这篇开创性论文:martincharvey.net/mch_custom_pages/Original/ToC.html
  • @MartynA 太棒了。以前没见过 - 多么棒的参考。
  • 如果您可以将指向它的链接编织到您的答案中,这样当最终来到这里的人正在搜索该主题时,它会显示在谷歌搜索中。
  • 反复阅读那篇论文,我终于制作了一个完整的并行线程库,解决了我现有和未来的所有需求。
  • @user30478,不,不是。我的图书馆没有出版,也可能永远不会出版。
猜你喜欢
  • 2022-01-19
  • 1970-01-01
  • 2019-08-07
  • 1970-01-01
  • 2023-03-12
  • 2016-07-07
  • 2016-03-25
  • 2017-10-09
相关资源
最近更新 更多