Ответ 1
Проблема заключается в том, что есть две очереди сообщений для сообщений. Результатом этого является то, что ваши опубликованные сообщения всегда обрабатываются перед любыми сообщениями Paint
, Input
или Timer
.
Это означает, что вы заполняете очередь сообщений несколькимистами тысячами сообщений. Эти сообщения всегда будут обработаны перед краской и пользовательскими сообщениями, что приведет к зависанию вашего приложения.
Общим способом решения этой проблемы является использование таймера; чтобы ваш код начинал очень короткий (например, 0 мс) таймер.
Разъяснение
Сообщения о таймере (
WM_TIMER
), такие как сообщения Paint (WM_PAINT
) и входные сообщения (например, WM_MOUSEMOVE, WM_KEYDOWN) обрабатываются после отправленных сообщений. Специальные сообщения таймера обрабатываются после ввода и сообщений краски.Это означает, что ваше приложение будет отвечать на пользовательские события и писать запросы до того, как оно обработает следующее сообщение
WM_TIMER
. Вы можете использовать это поведение, используя сообщение "Таймер" (и это сообщениеWM_TIMER
), вместо того, чтобы публиковать сообщение самостоятельно (что всегда будет иметь приоритет над красками и сообщениями ввода).
При срабатывании таймера они отправляют сообщение WM_TIMER
. Это сообщение всегда будет обрабатываться после любых сообщений ввода и рисования; что делает ваше приложение более отзывчивым.
Другой потенциал установки таймера состоит в том, что он заставляет вас "ставить в очередь" все обработанные элементы в списке (потокобезопасном). Вы объединяете несколько тысяч рабочих элементов в один "таймер", сохраняя систему от необходимости генерировать и обрабатывать тысячи ненужных сообщений.
Бонус-чат
... сообщения обрабатываются в следующем порядке:
- Отправленные сообщения
- Отправленные сообщения
- Вводные (аппаратные) сообщения и внутренние события системы
- Отправленные сообщения (снова)
- сообщения WM_PAINT
- сообщения WM_TIMER
Обновить. Вы должны не иметь опрос таймера, который просто расточительный и неправильный. Ваши потоки должны "установить флаг" (т.е. Таймер). Это позволяет вашему основному потоку фактически простаивать, только бодрствуя, когда есть что-то делать.
Обновить два:
Псевдокод, демонстрирующий, что порядок генерации сообщений не сохраняется:
//Code is public domain. No attribution required.
const
WM_ProcessNextItem = WM_APP+3;
procedure WindowProc(var Message: TMessage)
begin
case Message.Msg of
WM_Paint: PaintControl(g_dc);
WM_ProcessNextItem:
begin
ProcessNextItem();
Self.Invalidate; //Invalidate ourselves to trigger a wm_paint
//Post a message to ourselves so that we process the next
//item after any paints and mouse/keyboard/close/quit messages
//have been handled
PostMessage(g_dc, WM_ProcessNextItem, 0, 0);
end;
else
DefWindowProc(g_dc, Message.Msg, Message.wParam, Message.lParam);
end;
end;
Даже если я создаю WM_ProcessNextItem
после того, как я сгенерирую сообщение WM_PAINT
(т.е. Invalidate
), WM_PAINT
никогда не будет обработан, потому что перед ним всегда есть другое сообщение. И как сообщает MSDN, сообщения с краской появятся, только если нет других опубликованных сообщений.
Обновить три. Да, есть только очередь сообщений, но вот почему нам все равно:
Отправленные и отправленные сообщения
Терминология, которую я буду использовать здесь, нестандартна, но я использую ее, потому что я думаю, что она немного понятна, чем стандартная терминология. Для этой дискуссии я собираюсь сказать, что сообщения, связанные с потоком, делятся на три ковши, а не на более стандартные:
What I'll call them Standard terminology =========================== ============================= Incoming sent messages Non-queued messages Posted messages \_ Input messages / Queued messages
В действительности, распад сообщений более сложный, чем этот, но на данный момент мы придерживаемся вышеуказанной модели, потому что это "правда достаточно".
Старая новая вещь, практическое развитие на протяжении всей эволюции Windows
Раймонд Чен
ISBN 0-321-44030-7
Copyright © 2007 Pearson Education, Inc.
Глава 15 - Как выводить и восстанавливать сообщения окна, Страница 358
Легче представить, что есть две очереди сообщений. Никакие сообщения в "втором" не будут прочитаны, пока "первая" очередь не будет пуста; и ОП никогда не пропускает первую очередь. В результате все сообщения "краска" и "enter" во второй очереди обрабатываются, что приводит к зависанию приложения.
Это упрощение того, что на самом деле происходит, но оно достаточно близко для целей этого обсуждения.
Обновить четыре
Проблема не в том, что вы "наводняете" входную очередь своими сообщениями. Ваше приложение может быть невосприимчивым только с одним сообщением. Пока у вас есть одно отправленное сообщение в очереди, оно будет обработано перед любым другим сообщением.
Представьте, что произошла серия событий:
- Мышь перемещается (
WM_MOUSEMOVE
) - Левая кнопка мыши нажата вниз (
WM_LBUTTONDOWN
) - Отключена левая кнопка мыши (
WM_LBUTTONUP
) - Пользователь перемещает другое окно в сторону, заставляя ваше приложение перерисовываться (
WM_PAINT
) - В вашей ветке есть готовый элемент и отправляет уведомление (
WM_ProcessNextItem
)
Ваше приложение основной цикл сообщений (который вызывает GetMessage
) не получит сообщения в том порядке, в котором они были. Он получит сообщение WM_ProcessNextItem
. Это удаляет сообщение из очереди, оставляя:
-
WM_MOUSEMOVE
-
WM_LBUTTONDOWN
-
WM_LBUTTONUP
-
WM_PAINT
Пока вы обрабатываете свой элемент, пользователь перемещает мышь еще немного и нажимает случайно:
-
WM_MOUSEMOVE
-
WM_LBUTTONDOWN
-
WM_LBUTTONUP
-
WM_PAINT
-
WM_MOUSEMOVE
-
WM_MOUSEMOVE
-
WM_MOUSEMOVE
-
WM_MOUSEMOVE
-
WM_LBUTTONDOWN
-
WM_LBUTTONUP
-
WM_MOUSEMOVE
-
WM_MOUSEMOVE
-
WM_LBUTTONDOWN
-
WM_LBUTTONUP
-
WM_MOUSEMOVE
-
WM_MOUSEMOVE
-
WM_LBUTTONDOWN
-
WM_LBUTTONUP
В ответ на ваш WM_ProcessNextItem
вы отправляете другое сообщение себе. Вы делаете это, потому что сначала хотите обработать выдающиеся сообщения, прежде чем продолжить обработку большего количества элементов. Это добавит еще одно сообщение в очередь:
-
WM_MOUSEMOVE
-
WM_LBUTTONDOWN
-
WM_LBUTTONUP
-
WM_PAINT
-
WM_MOUSEMOVE
-
WM_MOUSEMOVE
-
WM_MOUSEMOVE
-
WM_MOUSEMOVE
-
WM_LBUTTONDOWN
-
WM_LBUTTONUP
-
WM_MOUSEMOVE
-
WM_MOUSEMOVE
-
WM_LBUTTONDOWN
-
WM_LBUTTONUP
-
WM_MOUSEMOVE
-
WM_MOUSEMOVE
-
WM_LBUTTONDOWN
-
WM_LBUTTONUP
-
WM_ProcessNextItem
Проблема начинает становиться очевидной. Следующий вызов GetMessage
будет извлекать WM_ProcessNextItem
, оставляя приложение с отставанием красок и входных сообщений:
-
WM_MOUSEMOVE
-
WM_LBUTTONDOWN
-
WM_LBUTTONUP
-
WM_PAINT
-
WM_MOUSEMOVE
-
WM_MOUSEMOVE
-
WM_MOUSEMOVE
-
WM_MOUSEMOVE
-
WM_LBUTTONDOWN
-
WM_LBUTTONUP
-
WM_MOUSEMOVE
-
WM_MOUSEMOVE
-
WM_LBUTTONDOWN
-
WM_LBUTTONUP
-
WM_MOUSEMOVE
-
WM_MOUSEMOVE
-
WM_LBUTTONDOWN
-
WM_LBUTTONUP
Решение состоит в том, чтобы воспользоваться обработкой сообщений вне очереди. Отправленные сообщения всегда обрабатываются до сообщений Paint/Input/Timer: не используйте отправленное сообщение. Вы можете думать о том, что очередь сообщений разделена на две группы: Отправленные сообщения и Вводные сообщения. Вместо того, чтобы вызывать ситуацию, когда очередь сообщений "Отправлено" никогда не разрешается пустым:
Posted messages Input messages
================== =====================
WM_ProcessNextItem WM_MOUSEMOVE
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_PAINT
Вы используете сообщение WM_TIMER
:
Posted messages Input messages
================== =====================
WM_MOUSEMOVE
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_PAINT
WM_TIMER
Nitpickers Corner. Это описание двух очередей не строго верно, но это достаточно верно. Как Windows доставляет сообщения в документированном порядке - это внутренняя деталь реализации, которая может быть изменена в любое время.
Важно отметить, что вы не опросили таймер, это было бы плохо. Вместо этого вы запускаете таймер с одним выстрелом в качестве механизма генерации сообщения WM_TIMER
. Вы делаете это, потому что знаете, что сообщения таймер не будут иметь приоритет над сообщениями paint или .
Использование таймера имеет другое преимущество использования. Между WM_PAINT
, WM_TIMER
и входящими сообщениями также выполняется обработка вне очереди:
- ввод сообщений, затем
-
WM_PAINT
, затем -
WM_TIMER
Если вы используете таймер для уведомления основного потока, вы также можете гарантировать, что вы будете обрабатывать краску и ввод данных раньше. Это гарантирует, что ваше приложение останется отзывчивым. Это улучшение удобства использования, и вы получаете его бесплатно.