Подтвердить что ты не робот

Я не понимаю, что делает Application.ProcessMessages в Delphi

Я - нуоби в Delphi, так что извините за любой глупый вопрос.

Мой руководитель объяснил мне, что Application.ProcessMessages предотвращает замораживание приложения и выделяет дополнительное вычислительное время. Но в документах этой команды всегда что-то объясняется о системе очереди, которая обрабатывается? Может кто-нибудь объяснить мне контекст?

4b9b3361

Ответ 1

Невозможно правильно ответить на этот вопрос.

Основным средством взаимодействия приложений Windows с операционной системой является система обмена сообщениями. Все, что происходит в приложении Windows, происходит в ответ на сообщение.

Например:

Если вы нажмете на экран, операционная система решит, какое приложение было нажата, и отправит сообщение этому приложению, указав, что он получил щелчок (вместе с местоположением этого клика).

Если окно перемещено и отображает часть вашего приложения под ним, операционная система отправляет сообщение, в котором ваше приложение будет перерисовывать.

Список можно продолжить. Все, что происходит, управляется сообщениями.

Теперь каждое приложение имеет один основной поток пользовательского интерфейса ( "Основной" поток), и этот поток имеет одну основную функцию - он работает в бесконечном цикле, который проверяет эти сообщения из операционной системы и затем выполняет необходимый код в ответ на эти сообщения.

Проблема

Вы приходите и начинаете писать заявку. Вы можете написать такой код:

procedure TForm1.Button1Click(Sender: TObject);
var i : Integer;
begin
  for i := 0 to 99999999999 do begin
    SolveTheProblemsOfTheWorld(i);
    CalculatePiToABillionPlaces;
  end;
end;

В большей структуре программы выполнение в основном потоке выглядит следующим образом:

  • Проверить наличие сообщений
  • Для каждого сообщения - выполнить связанные обработчики
  • Вернитесь к разделу Проверка сообщений (цикл)

Итак, этот цикл счастливо вибрирует, когда внезапно один из связанных обработчиков (Button1Click выше) начинает принимать очень много времени. Ключом к пониманию является то, что один обработчик сообщения должен завершиться до следующего запуска. Если вы нажмете на полосу прокрутки, например, и перетащите ее, но вы привязали обработчик к OnClick полосы прокрутки, которая завершила 10 секунд, тогда операция перетаскивания не будет видна вашим приложением до тех пор, пока этот обработчик клика не завершится. Тем временем очередь сообщений заполняется, и основной поток ничего не делает.

Конечно, вы испытали это - внезапно ваше приложение не отвечает на клики. Вы не можете взаимодействовать с ним, вы не можете переместить окно, если вы перетащите другое окно вокруг над ним, приложение даже не перекрасится - оно просто заполняется любым мусором, который вы оставили на нем.

Введите ProcessMessages

ленивое, страшное решение не помещать ваш длинный код в поток

То, что вы делаете, когда вы вызываете Application.ProcessMessages, находится в середине одного из этих обработчиков, инструктируя основной поток сделать перерыв, чтобы вернуться к проверке очереди сообщений и опорочить все сообщения, которые накапливались; обрабатывать любые щелчки, перемещения окон, входы, нажатия клавиш, перерисовывать, если необходимо, и т.д.

procedure TForm1.Button1Click(Sender: TObject);
var i : Integer;
begin
  for i := 0 to 99999999999 do begin
    SolveTheProblemsOfTheWorld(i);
    CalculatePiToABillionPlaces;
    Application.ProcessMessages;
  end;
end;

Это может показаться поверхностным, как разумная вещь, поскольку она позволяет вам реагировать на приложение во время продолжительного цикла. В конечном счете, однако, этот стиль программирования широко считается крайне плохой практикой по множеству очень веских причин. Достаточно сказать, что в любом месте, где вы соблазнились использовать Application.ProcessMessages, это надежный случай для перемещения этой работы в фоновый поток.

Для более подробной информации давайте посмотрим на фактический код:

procedure TApplication.ProcessMessages;
var
  Msg: TMsg;
begin
  while ProcessMessage(Msg) do {loop};
end;

Итак, когда вы вызываете этот вызов в Application.ProcessMessages, вы запускаете цикл, который один за другим, опорожняя сообщения из очереди сообщений (и выполняя весь код, который привязан к обработчикам, которые реагируют на эти сообщения) пока он не станет пустым. Когда он пуст и больше нет сообщений для обработки, элемент управления вернется к следующей строке в вашей программе.

Важным моментом для удаления является то, что текущее, гладкое взаимодействие, которое вы испытываете при использовании программы, представляет собой полную иллюзию, которая в точности зависит от того, как этот цикл сообщений обрабатывается как можно быстрее. Чем меньше время задержки между сообщением, отправленным вашему приложению и обрабатываемым сообщением, тем больше ваше приложение будет чувствовать, что оно живое и отзывчивое.

Именно по этой причине весь код, который подключен к обработчикам пользовательского интерфейса, должен быть быстрым. Длительные операции либо должны быть прерваны, чтобы обработка сообщений могла продолжаться (т.е.: Application.ProcessMessages), либо эти операции необходимо перенести в отдельный поток, где они могут выполняться, не связывая основной поток и не отнимая его от основной ответственности (который должен поддерживать пользовательский интерфейс).


Для действительно хорошей статьи по этой теме см.:

Одиссея с ключевыми словами by Peter Below

(Ссылка на кеш Google... сервер кажется занятым для меня)

Аннотация: В этой статье рассматривается путь сообщения нажатия клавиши через VCL. Вы узнаете, как реализуется ключевая обработка, как работают OnKey и какие точки вмешательства для программиста можно найти во всем процессе. Кроме того, объясняются такие вещи, как обработка сообщений, и вы узнаете, как трассировать сообщения в отладчике из цикла сообщений в их конечный пункт назначения.

Ответ 2

обмен сообщениями

Приложение Delphi VCL - это приложение Windows, и большая часть взаимодействия между компонентами Windows (такими как формы, поля редактирования, но также и скрытыми вещами, такими как таймеры) осуществляется с помощью вещей, называемых сообщениями.

Эти сообщения отправляются в специальную очередь. Вы можете отправлять такие сообщения напрямую (используя функции API SendMessage и PostMessage), но сообщения также отправляются косвенно, когда вы устанавливаете свойство, такое как Text TEdit.

Эти сообщения используются для уведомления элементов управления о том, что в программе произошли значительные события, чтобы они могли реагировать соответствующим образом. Эта концепция отправки и реагирования на сообщения является основным принципом парадигмы, известной как программирование на основе событий.

Многие современные операционные системы управляются событиями, например Windows. На самом деле Delphi VCL включает в себя множество функций Windows, и многие вещи, которые вы делаете, приводят к тому, что сообщения отправляются между элементами управления, которые уведомляют эти элементы управления о щелчках мыши, нажатиях клавиатуры, изменении настроек Windows, закрытии приложений, перемещении элементов управления и т.д.

Mainthread не обрабатывает сообщения при выполнении кода

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

Когда основной поток приложения находится в режиме ожидания (не выполняет код), он проверит очередь на наличие сообщений и обработает их. Пока выполняется другой не связанный код, это не может произойти. Рассмотрим этот пример:

Label1.Caption := 'A';
Sleep(5000); // Sleep is simulating a long, blocking process.
Label1.Caption := 'B';

Если вы выполните этот код, вы ожидаете, что метка получит заголовок "A", который изменится на "B" через пять секунд. Но это не так. Установка заголовка метки запускает перерисовку элемента управления через сообщение. Поскольку основной поток все еще заблокирован этим кодом (даже командой Sleep), сообщение еще не обработано.

Только после того, как пройдут 5 секунд и заголовок будет установлен на "B", основной поток станет бездействующим и выполнит перерисовки, которые мы инициировали, установив заголовок. Также обратите внимание, что другие действия, такие как нажатие кнопки или перетаскивание окна, откладываются до тех пор, пока не пройдут 5 секунд. Весь пользовательский интерфейс останавливается, пока основной поток выполняет этот код.

Application.ProcessMessages на помощь

Application.ProcessMessages просто заставит приложение очистить свою очередь сообщений, поэтому вы можете "исправить" код следующим образом:

Label1.Caption := 'A';
Application.ProcessMessages;
Sleep(5000);
Label1.Caption := 'B';

Обычно в вашем коде не будет просто Sleep, а будет много реальной обработки. При частом использовании Application.ProcessMessage вы можете поддерживать интерфейс приложения, даже когда вы выполняете код.

Но это немного грязно, основной поток (и интерфейс) все равно будет заблокирован, если вам не удастся втиснуть много вызовов Application.ProcessMessages. Помимо обработки сообщений рисования, обрабатываются и другие сообщения, поэтому приложение может обрабатывать событие щелчка прямо в середине вашего процесса.

Таким образом, документы, которые вы читаете, верны: Application.ProcessMessages очистит очередь сообщений. Ваш руководитель не совсем прав. Он не выделяет дополнительного времени на обработку, скажем, он просто интегрирует опустошение очереди сообщений в исполняемый код, в то время как обычно сообщения остаются в очереди до тех пор, пока приложение не станет бездействующим.

Внутренности

Внутренне само приложение делает то же самое все время. Его основная процедура, Application.Run, выглядит следующим образом (упрощенный псевдокод):

  repeat

    ProcessMessageFromTheQueue;
    If QueueIsEmpty then
      TellWindowsToWakeMeUpWhenANewMessageArrives;   

  until Terminated;

При обработке, выполняющей код, например, когда WM_MOUSECLICK сообщение WM_MOUSECLICK, он запускает (посредством некоторой магии Delphi VCL) ваш обработчик событий Button1Click. Поскольку это один поток, все выполняется последовательно, поэтому вы поймете, что ProcessMessageFromTheQueue возвращается только после завершения обработчика события. Delphi обрабатывает только одно сообщение за раз и выполняет следующее только после завершения обработки предыдущего.

Или это?

Application.ProcessMessages выглядит так:

  repeat

    ProcessMessageFromTheQueue;

  until QueueIsEmpty;

Таким образом, он выполняет почти то же самое, что и основной цикл приложения: он выбирает сообщение из очереди и обрабатывает его. Таким образом, это означает, что вы можете обрабатывать следующие сообщения, пока вы находитесь в процессе обработки последнего сообщения. Удобный прием, но он может легко запутать ваше приложение и вызвать всевозможные нежелательные побочные эффекты.

Правильный путь: темы

Большинство людей считают лучшим решением использовать отдельный поток для выполнения вашего кода и просто отправлять сигналы основному потоку, когда ему необходимо обновить метку или индикатор выполнения. Таким образом, основной поток все время простаивает и будет продолжать реагировать на щелчки мыши и т.д.

Тем не менее, для новичка очень тяжело. Некоторые вещи, которые нужно иметь в виду:

  • Поток (кроме основного потока) не должен напрямую обновлять графический интерфейс (например, поток не может установить Label1.Caption. Это может произойти сбой.
  • Переменные и другие ресурсы не должны изменяться несколькими потоками одновременно, поэтому вы должны принять меры предосторожности для предотвращения этого (критические разделы).
  • Вы должны (в большинстве случаев) предотвращать выполнение пользователем того же действия, которое уже выполняется.
  • Во многих случаях вы должны найти правильный способ прервать запущенный поток, если пользователь хочет отменить процесс или закрыть приложение.

В любом случае, этот ответ не должен быть полным курсом о многопоточности, но теперь, по крайней мере, вы знаете, для чего предназначен Application.ProcessMessages. Также обратите внимание, что многие считают его "злым", но, как новичок, вы можете попробовать его в любом случае, чтобы узнать его сильные и слабые стороны самостоятельно. Если вы это сделаете, я надеюсь, что эта информация, по крайней мере, вдохновит вас перейти к темам, как только вы наберетесь опыта для следующей главы.