Ответ 1
Почему мое приложение замерзает? - Введение в циклы сообщений и потоки
Это явление не изолировано от какого-либо конкретного сообщения. Это фундаментальное свойство цикла сообщений Windows: при обработке одного сообщения другое сообщение не может быть обработано одновременно. Это не так точно реализовано, но вы можете думать о нем как о очереди, где ваше приложение вытаскивает сообщения из очереди для обработки в обратном порядке, в которые они вставлены.
Следовательно, слишком длительная обработка любого сообщения приостанавливает обработку других сообщений, фактически замораживая ваше приложение (потому что он не может обрабатывать какие-либо данные). Единственный способ решить эту проблему - очевидный: не тратьте слишком много времени на обработку какого-либо одного сообщения.
Часто это означает делегирование обработки в фоновый поток. Вам все равно придется обрабатывать все сообщения в основном потоке, а потоки рабочего фона должны сообщать основному методу, когда они будут завершены. Все взаимодействие с GUI должно происходить в одном потоке, и это почти всегда основной поток в вашем приложении (поэтому его часто называют потоком пользовательского интерфейса).
(И чтобы ответить на возражение, поднятое в вашем вопросе, да, вы можете управлять несколькими потоками на однопроцессорных машинах. Вы не увидите никаких улучшений производительности, но это сделает интерфейс более отзывчивым. поток может выполнять только одну вещь за раз, но процессор может очень быстро переключаться между потоками, эффективно имитируя выполнение нескольких операций одновременно.)
Более полезная информация доступна здесь в этой статье MSDN: Предотвращение зависаний в приложениях Windows
Специальные случаи: Модальные контуры обработки событий
Некоторые операции с окнами в Windows - это модальные операции. Модаль - обычное слово в вычислениях, которое в основном относится к блокировке пользователя в конкретном режиме, где они не могут ничего сделать, пока не изменятся (то есть не выйдут из этих) режимов. Всякий раз, когда начинается модальная операция, разворачивается отдельный новый цикл обработки сообщений и происходит обработка сообщений (вместо вашего основного цикла сообщений) в течение всего режима. Обычными примерами этих модальных операций являются drag-and-drop, изменение размера окна и окна сообщений.
Учитывая пример изменения размера окна, ваше окно получает сообщение WM_NCLBUTTONDOWN
, которое вы передаете DefWindowProc
для обработки по умолчанию. DefWindowProc
показывает, что пользователь намеревается начать операцию перемещения или изменения размера и ввел петлю сообщения о перемещении/калибровке, расположенную где-то глубоко в недрах собственного кода Windows. Таким образом, ваш цикл сообщений приложения больше не работает, потому что вы вошли в новый режим перемещения/калибровки.
Windows запускает этот цикл перемещения/калибровки, пока пользователь интерактивно перемещает/выравнивает окно. Он делает это так, чтобы он мог перехватывать сообщения мыши и обрабатывать их соответственно. Когда операция перемещения/калибровки завершается (например, когда пользователь отпускает кнопку мыши или нажимает клавишу Esc), управление возвращается к вашему коду приложения.
Стоит отметить, что вы получили уведомление о том, что это изменение режима произошло через сообщение WM_ENTERSIZEMOVE
; соответствующее сообщение WM_EXITSIZEMOVE
указывает, что завершен цикл обработки модальных событий. Это позволяет создать таймер, который будет продолжать генерировать сообщения WM_TIMER
, которые может обрабатывать ваше приложение. Фактические данные о том, как это реализовано, относительно неважны, но быстрое объяснение заключается в том, что DefWindowProc
продолжает отправлять сообщения WM_TIMER
в ваше приложение внутри своего собственного цикла обработки модальных событий. Используйте функцию SetTimer
, чтобы создать таймер в ответ на сообщение WM_ENTERSIZEMOVE
, а KillTimer
function, чтобы уничтожить его в ответ на сообщение WM_EXITSIZEMOVE
.
Я только указываю на полноту. В большинстве приложений Windows, которые я написал, мне никогда не нужно было это делать.
Итак, что не так с моим кодом?
Помимо всего этого, поведение, которое вы описываете в вопросе, необычно. Если вы создаете новое пустое приложение Win32 с использованием шаблона Visual Studio, я сомневаюсь, что вы сможете воспроизвести это поведение. Не видя остальной части вашей оконной процедуры, я не могу сказать, блокируете ли вы какие-либо сообщения (как обсуждалось выше), но часть, которую я вижу в вопросе, неверна. Вы всегда должны называть DefWindowProc
для сообщений, которые вы явно не обрабатываете самостоятельно.
В этом случае вас может обмануть мысль, что вы это делаете, но WM_SYSCOMMAND
может иметь множество разных значений для его wParam
. Вы используете только один из них, SC_CLOSE
. Все остальные просто игнорируются, потому что вы return 0
. Это включает в себя все функциональные возможности перемещения и изменения окна (например, SC_MOVE
, SC_SIZE
, SC_MINIMIZE
, SC_RESTORE
, SC_MAXIMIZE
и т.д. И т.д.).
И действительно нет веских оснований обращаться с WM_SYSCOMMAND
самостоятельно; просто пусть DefWindowProc
позаботится об этом для вас. Единственный раз, когда вам нужно обрабатывать WM_SYSCOMMAND
, - это когда вы добавили пользовательские элементы в меню окна, и даже тогда вы должны передать все команды, которые вы не распознаете, на DefWindowProc
.
Основная процедура окна должна выглядеть так:
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_CLOSE:
DestroyWindow(hWnd);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
Также возможно, что ваш цикл сообщений неверен. Идиоматический контур сообщения Win32 (расположенный рядом с нижней частью вашей функции WinMain
) выглядит следующим образом:
BOOL ret;
MSG msg;
while ((ret = GetMessage(&msg, nullptr, 0, 0)) != 0)
{
if (ret != -1)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
// An error occurred! Handle it and bail out.
MessageBox(nullptr, L"Unexpected Error", nullptr, MB_OK | MB_ICONERROR);
return 1;
}
}
Вам не нужны никакие крючки. Документация MSDN на них очень хорошая, но вы правы: они сложны. Держитесь подальше, пока не получите лучшее представление о модели программирования Win32. Это редкий случай, когда вам нужна функциональность, предоставляемая крюком.