Темная сторона применения. ProcessMessages

Статья представлена ​​Маркусом Юнгласом

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

Если вы сделаете это, вы заметите, что Ваше приложение заблокировано. Ваша форма больше не может быть перемещена, и кнопки не показывают никаких признаков жизни. Вроде бы разбился.

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

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

Распространенным решением для такого рода проблем является вызов «Приложение. ProcessMessages». «Приложение» - это глобальный объект класса TApplication.

instagram viewer

Приложение. Processmessages обрабатывает все ожидающие сообщения, такие как движения окна, нажатия кнопок и так далее. Это обычно используется как простое решение, чтобы ваше приложение работало.

К сожалению, механизм, лежащий в основе «ProcessMessages», имеет свои особенности, что может вызвать большую путаницу!

Что такое ProcessMessages?

PprocessMessages обрабатывает все ожидающие системные сообщения в очереди сообщений приложений. Windows использует сообщения, чтобы «общаться» со всеми запущенными приложениями. Взаимодействие с пользователем доводится до формы через сообщения, и «ProcessMessages» обрабатывает их.

Если мышь нажимает на TButton, например, ProgressMessages делает все, что должно произойти в этом событии, например перекрасить кнопку в «нажатое» состояние и, конечно, вызвать процедуру обработки OnClick (), если вы ее назначили.

Вот в чем проблема: любой вызов ProcessMessages может снова содержать рекурсивный вызов любого обработчика событий. Вот пример:

Используйте следующий код для обработчика события OnClick кнопки («работа»). Формула for имитирует длительное задание на обработку с некоторыми вызовами ProcessMessages время от времени.

Это упрощено для лучшей читаемости:

{в MyForm:}
WorkLevel: целое число;
{OnCreate:}
WorkLevel: = 0;
процедура TForm1.WorkBtnClick (Отправитель: TObject);
вар
цикл: целое число;
начать
inc (WorkLevel);
за цикл: = 1 в 5 делать
начать
Memo1.Lines. Добавить ('- Работа' + IntToStr (WorkLevel) + ', Cycle' + IntToStr (цикл);
Заявка. ProcessMessages;
сон (1000); // или другая работа
конец;
Memo1.Lines. Добавить («Работа» + IntToStr (WorkLevel) + «Завершено.»);
dec (WorkLevel);
конец;

БЕЗ «ProcessMessages» в заметку записываются следующие строки, если кнопка была нажата ДВАЖДЫ в течение короткого времени:

 - работа 1, цикл 1
- Работа 1, Цикл 2
- работа 1, цикл 3
- работа 1, цикл 4
- работа 1, цикл 5
Работа 1 закончена.
- работа 1, цикл 1
- Работа 1, Цикл 2
- работа 1, цикл 3
- работа 1, цикл 4
- работа 1, цикл 5
Работа 1 закончена.

Пока процедура занята, форма не показывает никакой реакции, но второй щелчок был помещен в очередь сообщений Windows. Сразу после завершения «OnClick» он будет вызван снова.

ВКЛЮЧАЯ «ProcessMessages», выходные данные могут быть очень разными:

 - работа 1, цикл 1
- Работа 1, Цикл 2
- работа 1, цикл 3
- работа 2, цикл 1
- Работа 2, Цикл 2
- работа 2, цикл 3
- Работа 2, Цикл 4
- работа 2, цикл 5
Работа 2 закончена.
- работа 1, цикл 4
- работа 1, цикл 5
Работа 1 закончена.

На этот раз форма, кажется, снова работает и принимает любое взаимодействие с пользователем. Таким образом, кнопка нажата наполовину во время вашей первой «рабочей» функции СНОВА, которая будет обработана мгновенно. Все входящие события обрабатываются как любой другой вызов функции.

Теоретически, во время каждого вызова «ProgressMessages» ЛЮБОЕ количество кликов и сообщений пользователя может происходить «на месте».

Так что будьте осторожны с вашим кодом!

Другой пример (в простом псевдокоде!):

процедура OnClickFileWrite ();
вар myfile: = TFileStream;
начать
myfile: = TFileStream.create ('myOutput.txt');
пытаться
пока BytesReady> 0 делать
начать
мой файл. Запись (DataBlock);
dec (BytesReady, sizeof (DataBlock));
Блок данных [2]: = # 13; {тестовая строка 1}
Заявка. ProcessMessages;
Блок данных [2]: = # 13; {тестовая строка 2}
конец;
наконец
myfile.free;
конец;
конец;

Эта функция записывает большой объем данных и пытается «разблокировать» приложение, используя «ProcessMessages» каждый раз, когда записывается блок данных.

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

Возможно, ваше приложение исправит ошибки, например освободит буферы

Как возможный результат, «Блок данных» будет освобожден, и первый код «внезапно» вызовет «Нарушение прав доступа» при доступе к нему. В этом случае: тестовая строка 1 будет работать, тестовая строка 2 будет аварийной.

Лучший способ:

Чтобы упростить эту задачу, вы можете установить для всей формы значение «enabled: = false», которое блокирует весь пользовательский ввод, но НЕ показывает это пользователю (все кнопки не выделены серым цветом).

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

Вы могли бы отключить контейнерные дочерние элементы управления при изменении свойства Enabled.

Как следует из названия класса «TNotifyEvent», его следует использовать только для кратковременных реакций на событие. Для кода, требующего много времени, лучше всего IMHO поместить весь «медленный» код в собственный поток.

Что касается проблем с «PrecessMessages» и / или включения и выключения компонентов, использование вторая нить кажется не слишком сложным.

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

Вот и все. В следующий раз вы добавите «Приложение. ProcessMessages ", подумайте дважды;)

instagram story viewer