Почему MessageBox не блокирует приложение в синхронизированном потоке?
Насколько я понимаю и знаю метод класса TThread, если вы синхронизируете свой код, он фактически выполняется в основном потоке приложений (так же, как таймер /buttonclick/ и т. Д.). Я играл по этому поводу и заметил, что MessageBox НЕ блокирует основное приложение, однако режим сна работает так, как ожидалось. Это почему?
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;
3 ответа
Этот ответ состоит из двух частей.
Часть 1 хорошо объяснена в Если MessageBox()/related является синхронным, почему мой цикл сообщений не останавливается?, Функция MessageBox не блокирует, она просто создает диалоговое окно со своим собственным циклом сообщений.
Часть 2 объясняется в документации MessageBox.
hWnd: дескриптор окна владельца создаваемого окна сообщения. Если этот параметр NULL, окно сообщения не имеет окна владельца.
Когда вы отображаете модальное диалоговое окно, Windows отключает своего владельца, но если вы передадите 0 для первого параметра, нет владельца и нечего отключать. Поэтому ваша программа будет продолжать обрабатывать сообщения (и реагировать на них), пока отображается окно сообщения.
Чтобы изменить это поведение, передайте дескриптор формы в качестве первого параметра. Например:
procedure TTestThread.SynchThread;
begin
MessageBoxA (Form1.Handle, 'Hello', 'Test', 0);
end;
Я подозреваю, что вопрос сводится к тому, что вы имеете в виду, когда говорите:
Окно сообщения не блокирует основное приложение.
Я понимаю, что когда вы показываете окно сообщения, с вашей формой VCL все еще можно взаимодействовать. Проблема здесь не связана с потоками, и я предлагаю удалить их из уравнения. Ваше понимание того, что Synchronize
делает это звук.
Проблема полностью связана с концепцией владельца окна и с тем, как модальные диалоговые окна ведут себя по отношению к своим владельцам. Обратите внимание, что под владельцем я не имею в виду свойство Delphi TComponent.Owner
, но я имею в виду Windows API значение владельца.
Создайте приложение 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 можно взаимодействовать.
У Раймонда Чена есть длинная серия статей о модальности в его блоге Old New Thing:
- Модальность, часть 1: UI-модальность против кодовой модальности
- Модальность, часть 2. Код-модальность против UI-модальности
- Модальность, часть 3: сообщение WM_QUIT
- Модальность, часть 4: Важность установки правильного владельца для модального интерфейса
- Модальность, часть 5: Установка правильного владельца модального интерфейса
- Модальность, часть 6: Взаимодействие с программой, которая стала модальной
- Модальность, часть 7: синхронизированный MessageBox, дешевая версия
- Модальность, часть 8: синхронизированный MessageBox, лучшая версия
- Модальность, часть 9: Установка правильного владельца модального интерфейса, практический экзамен
Синхронизировать выполнит код в Mainthread.
Хорошее объяснение можно найти здесь Синхронизация в классе Delphi TThread
Вам просто нужно запретить пользователю взаимодействовать с формами вашего приложения, например. от
procedure TTestThread.SynchThread;
begin
MessageBoxA (0, 'Hello', 'Test', MB_TASKMODAL);
end;
использование MessageBoxA, как вы сделали, не помешает Mainthread реагировать на события, вызванные взаимодействием ueser с вашими формами, просто попробуйте
procedure TForm4.Button2Click(Sender: TObject);
begin
MessageBoxA (0, 'Hello', 'Test', 0);
// vs
// MessageBoxA (0, 'Hello', 'Test', MB_TASKMODAL);
end;
то, что синхронизация будет выполняться в основном потоке, может быть показано (IMHO)
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;