Почему 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;

person Benjamin Weiss    schedule 29.03.2013    source источник
comment
Объясните, что вы имеете в виду под блокировкой основного приложения   -  person David Heffernan    schedule 29.03.2013
comment
связанные: delphigroups.info/2/11/544013.html   -  person Z80    schedule 09.09.2020


Ответы (3)


Этот ответ состоит из двух частей.

Часть 1 хорошо объяснена в Если MessageBox () / related синхронны, почему мой цикл сообщений не зависает?. Функция MessageBox не блокирует, она просто создает диалоговое окно с собственным циклом сообщений.

Часть 2 объясняется в Документация MessageBox.

hWnd: дескриптор окна владельца создаваемого окна сообщения. Если этот параметр имеет значение ПУСТО (NULL), окно сообщения не имеет окна владельца.

Когда вы показываете модальное диалоговое окно, Windows отключает его владельца, но если вы передадите 0 для первого параметра, владельца нет и нечего отключать. Следовательно, ваша программа будет продолжать обрабатывать сообщения (и реагировать на них), пока отображается окно сообщения.

Чтобы изменить это поведение, передайте дескриптор формы в качестве первого параметра. Например:

procedure TTestThread.SynchThread;
begin
  MessageBoxA (Form1.Handle, 'Hello', 'Test', 0);
end;
person gabr    schedule 29.03.2013
comment
Ваша часть 2 не точна. Отсутствие владельца не меняет, какой цикл сообщений выводит сообщения. MessageBox по-прежнему является синхронным, и до тех пор, пока он не вернется, его цикл обработки сообщений снимает сообщения из очереди. Когда вы передаете окно владельца в модальный диалог, модальный диалог отключает владельца, его владельца и так далее. - person David Heffernan; 29.03.2013

Я подозреваю, что вопрос сводится к тому, что вы имеете в виду, когда говорите:

Окно сообщения не блокирует основное приложение.

Я понимаю, что это означает, что когда вы показываете окно сообщения, с вашей формой VCL все еще можно взаимодействовать. Проблема здесь не связана с потоками, и я предлагаю удалить их из уравнения. Ваше понимание того, что делает Synchronize, хорошее.

Проблема полностью связана с концепцией владельца окна , и как модальные диалоговые окна ведут себя по отношению к своим владельцам. Обратите внимание, что под владельцем я не имею в виду свойство Delphi TComponent.Owner, но я имею в виду значение API Windows владелец.

Создайте приложение 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:

person David Heffernan    schedule 29.03.2013

Синхронизация выполнит код в Mainthread.
Хорошее объяснение можно найти здесь Синхронизация в классе Delphi TThread

Вам просто нужно будет запретить пользователю взаимодействовать с формами вашего приложения, например. к

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

использование MessageBoxA, как и вы, не помешает Mainthread реагировать на те события, которые инициируются взаимодействием пользователя с вашими формами, просто попробуйте

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

MessageBoxA

что синхронизация будет выполняться в основном потоке, может быть показано (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;
person bummi    schedule 29.03.2013
comment
Итак, почему за это отказывают, мне кажется, что параметр MB_TASKMODAL - хороший намек. @bummi, почему бы не передать дескриптор окна главной формы? - person Remko; 29.03.2013
comment
@Remko перед моим первым редактированием у меня тоже был звонок с application.handle. Голос против будет вызван разными точками зрения на блокировку. - person bummi; 29.03.2013
comment
Кто-нибудь знает, как MB_TASKMODAL работает? Отключает ли он все окна верхнего уровня, принадлежащие вызывающему процессу? - person David Heffernan; 29.03.2013
comment
@DavidHeffernan - вот как я понимаю MSDN, но вы не спросите, не прав ли я с моей точки зрения? - person bummi; 29.03.2013
comment
@bummi Нет, я не пытаюсь расставить для тебя ловушки? Я прошу по простой и простой причине, что я не знаю ответа! :-) - person David Heffernan; 29.03.2013
comment
Документы @DavidHeffernan msdn довольно ясны: все окна верхнего уровня, принадлежащие текущему потоку, отключены - person Remko; 29.03.2013