РАССЫЛКА ЯВЛЯЕТСЯ ЧАСТЬЮ ПРОЕКТА RSDN , НА САЙТЕ КОТОРОГО
ВСЕГДА МОЖНО НАЙТИ ВСЮ НЕОБХОДИМУЮ РАЗРАБОТЧИКУ ИНФОРМАЦИЮ, СТАТЬИ, ФОРУМЫ,
РЕСУРСЫ, ПОЛНЫЙ АРХИВ ПРЕДЫДУЩИХ ВЫПУСКОВ РАССЫЛКИ И МНОГОЕ ДРУГОЕ.
Когда-то, во времена Windows 3.1, все приложения имели одну
общую очередь событий ввода. Если одно из приложений по каким-то причинам
переставало обрабатывать сообщения, это легко могло привести к зависанию всей
системы. Времена изменились, и теперь, в 32-битной и многопоточной Windows,
каждый поток имеет свою очередь ввода. Однако, зависающие приложения не
исчезли. Как отличить приложения, которые добросовестно обрабатывают сообщения
от тех, что перестали реагировать на запросы пользователя, мы и обсудим в этой
статье.
Мы рассмотрим два способра решения этой задачи. Один из них, с
использованием функции SendMessageTimeout, - тот, который Microsoft
рекомендует в своей базе знаний. Другой - тот, который на самом деле используют
приложения Microsoft. В обоих случаях функция для определения зависших
приложений будет иметь следующий прототип:
BOOL IsAppHung(
IN HWND hWnd, // идентификатор окна
OUT PBOOL pbHung // указатель на флаг
);
Здесь, hWnd - идентификатор главного окна приложения, а pbHung -
указатель на переменную типа BOOL, которую функция устанавливает в TRUE, если
приложение действительно не отвечает.
Использование функции SendMessageTimeout
Функция SendMessageTimeout посылает сообщение указанному
окну. Функция примечательна тем, что в случае, если окно принадлежит другому
потоку, она не возвращает управления до тех пор, пока окно не обработает
сообщение, либо не истечет указанный интервал времени. Кроме того, если указан
флаг SMTO_ABORTIFHUNG, и похоже, что вызываемое приложение зависло, функция
возвращает управление сразу же, не дожидаясь, когда истечет таймаут. Именно это
свойство SendMessageTimeout позволяет использовать ее для определения
зависших приложений.
BOOL IsAppHung_SMTO(
IN HWND hWnd,
OUT PBOOL pbHung
)
{
_ASSERTE(pbHung != NULL);
*pbHung = FALSE;
if (!IsWindow(hWnd))
return SetLastError(ERROR_INVALID_PARAMETER), FALSE;
DWORD_PTR dwResult;
if (!SendMessageTimeout(hWnd, WM_NULL, 0, 0,
SMTO_ABORTIFHUNG|SMTO_BLOCK, 500,
&dwResult))
*pbHung = TRUE;
return TRUE;
}
Мы посылаем сообщение WM_NULL главному окну проверяемого
приложения. Если это сообщение будет успешно доставлено окну, то окно его
просто проигнорирует, если же SendMessageTimeout вернет ошибку, мы
считаем, что приложение зависло.
Использование недокументированных функций IsHungAppWindow и IsHungThread
Как уже было замечено, программы Microsoft, в частности Windows
NT Task Manager и Windows 95 Task List не используют SendMessageTimeout для
определения зависших приложений. Вместо этого они пользуются двумя
недокументированными функциями.
В Windows NT/2000 библиотека USER32.DLL экспортирует функцию IsHungAppWindow,
прототип которой приведен ниже. Task Manager использует эту функцию, чтобы
обнаружить приложения, которые перестали отвечать на сообщения.
BOOL WINAPI IsHungAppWindow(
IN HWND hWnd // идентификатор окна
);
В Windows 9x/Me USER32.DLL экспортирует аналогичную функцию под
названием IsHungThread, которая отличается от своего NT-собрата тем, что
принимает не идентификатор окна, а идентификатор потока.
BOOL WINAPI IsHungThread(
IN DWORD dwThreadId // идентификатор потока
);
Вооруженные этими знаниями, мы можем написать другую функцию для
определения зависших приложений:
Новая функция начинает свою работу с определения версии Windows.
Если программа выполняется на Windows NT, то она находит и вызывает функцию IsHungAppWindow.
При работе на Windows 9x, функция сперва определяет идентификатор потока,
которому принадлежит указанное окно, а затем вызывает IsHungThread. Все
очень просто.
А что делать в Windows NT в случае отсутствия у процесса окна? В
таком случае общего решения нет. Все зависит от того, что вкладывается в
понятие "не отвечает". Для интерактивных процессов тут все просто - не
обрабатывает сообщения, значит "не отвечает". Но если речь идет о серверном
процессе, то так просто уже не получится. Например, для Web-сервера, если не
откликается на HTTP-запрос, значит, "не отвечает". Для других видов процессов
другая семантика понятия "не отвечает".
Если есть контроль над исходным кодом процесса, то проще всего реализовать
watchdog counter - переменную в реестре, которая периодически увеличивается в
реестре процессом. Если счетчик работает, значит, еще жив, если нет - убить и
перезапустить.
Если доступа к исходникам нет, то надо разбираться в логике процесса, например,
для Web-сервера, можно посылать запрос HEAD на главную страницу. Получили
ответ, значит, еще жив, если нет - убить и перезапустить.
Заключение
Таким образом, мы рассмотрели два способа для определения
приложений, которые перестали отвечать на запросы пользователей. В процессе
тестирования я обнаружил, что функция SendMessageTimeout для только что
повисшего приложения возвращает управление с небольшой задержкой, хотя и
сообщает о том, что приложение зависло. Через некоторое время, когда система
убеждается, что приложение действительно повисло, она начинает возвращать
управление сразу. IsHungAppWindow и IsHungThread не имеют этого
недостатка.
Чтобы проверить описанные в этой статье функции, вы можете
воспользоваться тестовым приложением Process Viewer. В качестве зависающего
приложения можно использовать небольшую программу HUNGTEST, которая также
прилагается к этой статье.