【问题标题】:Why does a MessageBox not block the Application on a synchronized thread?为什么 MessageBox 不会阻塞同步线程上的应用程序?
【发布时间】:2013-03-19 18:42:01
【问题描述】:

据我了解并知道 TThread 类的方法,如果您同步代码,它实际上会在主应用程序线程中执行(就像计时器/按钮单击/等) 我一直在玩,并注意到 MessageBox 不会阻止主应用程序,但是 sleep 会按预期进行。这是为什么呢?

type
  TTestThread = class(TThread)
  private
    procedure SynchThread;
  protected
    procedure Execute; override;
  public
    constructor Create(CreateSuspended: Boolean);
  end;

procedure TTestThread.SynchThread;
begin
 MessageBoxA (0, 'Hello', 'Test', 0);
end;

procedure TTestThread.Execute;
begin
 Synchronize (SynchThread)
end;

constructor TTestThread.Create(CreateSuspended: Boolean);
begin
  inherited;
  FreeOnTerminate := True;
end;

procedure StartThread;
var
 TestThread : TTestThread;
begin
 TestThread := TTestThread.Create (FALSE);
end;

【问题讨论】:

标签: delphi vcl tthread


【解决方案1】:

这个答案有两个部分。

第 1 部分在 If MessageBox()/related are synchronous, why doesn't my message loop freeze? 中有很好的解释。 MessageBox 函数没有阻塞,它只是创建一个带有自己的消息循环的对话框。

第 2 部分在MessageBox documentation 中进行了解释。

hWnd:要创建的消息框的所有者窗口句柄。 如果这样 参数为NULL,消息框没有所有者窗口。

当您显示模态对话框时,Windows 会禁用其所有者,但如果您为第一个参数传递 0,则没有所有者,也没有可禁用的内容。因此,在显示消息框时,您的程序将继续处理消息(并对它们作出反应)。

要更改此行为,请将表单的句柄作为第一个参数传递。例如:

procedure TTestThread.SynchThread;
begin
  MessageBoxA (Form1.Handle, 'Hello', 'Test', 0);
end;

【讨论】:

  • 您的第 2 部分不准确。没有所有者不会改变哪个消息循环拉出消息。 MessageBox 仍然是同步的,在它返回之前,它的消息循环会拉出排队的消息。当您将所有者窗口传递给模式对话框时,模式对话框会禁用所有者、其所有者等。
【解决方案2】:

我怀疑问题归结为你说的意思:

消息框不会阻塞主应用程序。

我的意思是当你显示消息框时,你的 VCL 表单仍然可以交互。这里的问题与线程无关,我建议我们将它们从等式中删除。您对 Synchronize 所做的事情的理解是正确的。

这个问题完全与window's owner 的概念有关,以及模式对话框窗口相对于其所有者的行为方式。请注意,这里的所有者,我不是指 Delphi 属性 TComponent.Owner,而是指 Windows API 的 owner 含义。

创建一个 VCL 应用并在表单上放置两个按钮。添加以下OnClick 处理程序。

procedure TForm1.Button1Click(Sender: TObject);
begin
  MessageBox(0, 'Not owned', nil, MB_OK);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  MessageBox(Handle, 'Owned by the VCL form', nil, MB_OK);
end;

现在观察单击Button1 时会发生什么。消息框显示,但您仍然可以单击 VCL 表单。并与Button2 进行比较。当它显示消息框时,无法与 VCL 表单交互。

当显示模式对话框窗口时,对话框窗口会禁用其所有者。在Button2 的情况下,所有者是VCL 表单。一旦表单被禁用,您就无法与之交互。在Button1 的情况下,没有所有者,因此模态对话框窗口不会禁用任何其他窗口。这就是为什么可以与 VCL 表单交互的原因。

Raymond Chen 在他的 Old New Thing 博客上发表了关于模态的长篇系列文章:

【讨论】:

    【解决方案3】:

    Synchronize 将执行主线程中的代码。
    一个很好的解释可以在这里找到Synchronization in Delphi TThread class

    您只需要阻止用户与您的应用程序的表单进行交互,例如。由

    procedure TTestThread.SynchThread;
    begin
    MessageBoxA (0, 'Hello', 'Test', MB_TASKMODAL);      
    end;
    

    像你一样使用 MessageBoxA,不会阻止主线程对用户与表单交互触发的那些事件做出反应,只需尝试

    procedure TForm4.Button2Click(Sender: TObject);
    begin
        MessageBoxA (0, 'Hello', 'Test', 0);
       // vs
       //  MessageBoxA (0, 'Hello', 'Test', MB_TASKMODAL);
    end;
    

    MessageBoxA

    同步将在主线程中执行可以显示(恕我直言)

    type
      TTestThread = class(TThread)
      private
        FSync:Boolean;
        FCalled:TDateTime;
        procedure SynchThread;
      protected
        procedure Execute; override;
      public
        constructor Create(CreateSuspended: Boolean;sync:Boolean);
      end;
    
    procedure TTestThread.SynchThread;
    begin
     MessageBox (0,PChar(DateTimeToStr(FCalled)+#13#10+DateTimeToStr(Now)),'Hello' , 0);
    end;
    
    procedure TTestThread.Execute;
    begin
     sleep(100); // give Caller Time to fell asleep
     if Fsync then Synchronize (SynchThread) else SynchThread;
    end;
    
    constructor TTestThread.Create(CreateSuspended: Boolean;sync:Boolean);
    begin
      inherited Create(CreateSuspended);
      FSync := Sync;
      FCalled :=Now;
      FreeOnTerminate := True;
    end;
    
    procedure StartThread(sync:Boolean);
    var
     TestThread : TTestThread;
    begin
     TestThread := TTestThread.Create (FALSE,sync);
    end;
    
    procedure TForm4.RunUnsynchronizedClick(Sender: TObject);
    begin
       StartThread(false);// no sync
       Sleep(5000);       // Stop messageloop
    end;
    
    procedure TForm4.RunSynchronizedClick(Sender: TObject);
    begin
       StartThread(true); // sync
       Sleep(5000);       // Stop messageloop
    end;
    

    【讨论】:

    • 那么为什么这被否决了,在我看来 MB_TASKMODAL 参数是一个很好的提示。 @bummi 为什么不传递主窗体的窗口句柄?
    • @Remko 在我第一次编辑之前,我也曾与 application.handle 通话。反对票将由关于“阻止”的不同观点引起。
    • 有人知道MB_TASKMODAL 的工作原理吗?它会禁用调用进程拥有的所有顶级窗口吗?
    • @DavidHeffernan 这就是我理解 MSDN 的方式,但你不会问我的观点是否错了?
    • @DavidHeffernan msdn 文档很清楚:所有属于当前线程的顶级窗口都被禁用
    猜你喜欢
    • 2012-10-22
    • 1970-01-01
    • 2021-10-19
    • 1970-01-01
    • 2016-06-26
    • 2017-08-13
    • 2021-08-29
    • 1970-01-01
    相关资源
    最近更新 更多