问题
As far as I understand and know the method of the TThread Class, if you synchronize your code, it actually get's executed in the main Application Thread (just like a timer/buttonclick/etc.) I've been playing around and noticed that a MessageBox DOES NOT block the main application, however sleep does just as expected. Why is that?
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;
回答1:
There are two parts to this answer.
Part 1 is nicely explained in If MessageBox()/related are synchronous, why doesn't my message loop freeze?. The MessageBox function is not blocking, it merely creates a dialog box with its own message loop.
Part 2 is explained in the MessageBox documentation.
hWnd: A handle to the owner window of the message box to be created. If this parameter is NULL, the message box has no owner window.
When you display a modal dialog, Windows disables its owner, but if you pass 0 for the first parameter, there is no owner and nothing to disable. Therefore, your program will continue to process messages (and react to them) while the message box is displayed.
To change this behaviour, pass form's handle as a first parameter. For example:
procedure TTestThread.SynchThread;
begin
MessageBoxA (Form1.Handle, 'Hello', 'Test', 0);
end;
回答2:
I suspect that the question boils down to what you mean when you say:
A message box does not block the main application.
What I take this to mean is that when you show the message box, your VCL form can still be interacted with. The issue here is unrelated to threads and I suggest we remove them from the equation. Your understanding of what Synchronize
does is sound.
The issue is entirely related to the concept of a window's owner, and how modal dialog windows behave with respect to their owners. Note that by owner, I don't mean the Delphi property TComponent.Owner
, but I mean the Windows API meaning of owner.
Create a VCL app and drop two buttons on the form. Add the following OnClick
handlers.
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;
Now observe what happens when you click on Button1
. The message box shows, but you can still click on the VCL form. And compare with Button2
. When it shows the message box, the VCL form cannot be interacted with.
When a modal dialog window is shown, the dialog window disables its owner. In the case of Button2
, the owner is the VCL form. And once the form is disabled, you cannot interact with it. In the case of Button1
, there is no owner and so the modal dialog window does not disable any other window. That's why the VCL form can be interacted with.
Raymond Chen has a long series on modality at his Old New Thing blog:
- Modality, part 1: UI-modality vs code-modality
- Modality, part 2: Code-modality vs UI-modality
- Modality, part 3: The WM_QUIT message
- Modality, part 4: The importance of setting the correct owner for modal UI
- Modality, part 5: Setting the correct owner for modal UI
- Modality, part 6: Interacting with a program that has gone modal
- Modality, part 7: A timed MessageBox, the cheap version
- Modality, part 8: A timed MessageBox, the better version
- Modality, part 9: Setting the correct owner for modal UI, practical exam
回答3:
Synchronize will execute the code in the Mainthread.
A good explanation can be found here Synchronization in Delphi TThread class
You just will have to prevent user from interacting with the forms of your application, eg. by
procedure TTestThread.SynchThread;
begin
MessageBoxA (0, 'Hello', 'Test', MB_TASKMODAL);
end;
using MessageBoxA as you did, won't prevent the Mainthread from reacting on those events triggerd by ueser interaction with your forms, just try
procedure TForm4.Button2Click(Sender: TObject);
begin
MessageBoxA (0, 'Hello', 'Test', 0);
// vs
// MessageBoxA (0, 'Hello', 'Test', MB_TASKMODAL);
end;
MessageBoxA
that synchronize will be executed in the main thread can be shown (IMHO) by
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;
来源:https://stackoverflow.com/questions/15696885/why-does-a-messagebox-not-block-the-application-on-a-synchronized-thread