Приложения, которые ломаются после выхода из сна — как работают события питания Windows и как строить бизнес-приложения, которые это переживают

· · Windows, Управление питанием, Разработка под Windows, Бизнес-приложения, Управление устройствами, Устранение неполадок, Win32 API

«Закрыл ноутбук, открыл на следующее утро — и бизнес-приложение полное ошибок.» «Приложение мониторинга оборудования теряет данные только после обеда.» «Резидентный инструмент, который выгружает в Excel, иногда останавливается на ошибке соединения.» — У этих заявок один общий подозреваемый. Сон.

Бизнес-приложения эпохи, когда мейнстримом были настольные ПК, писались на негласном допущении, что «ПК остаётся включённым». Главное поле сегодня — ноутбук, и по умолчанию он засыпает через несколько минут бездействия. На машине с Modern Standby сама семантика сна ушла от традиционной модели. Рассчитанная на разработчиков, которые пишут бизнес-приложения и ПО управления оборудованием под Windows, эта статья собирает по первичным источникам, о чём ОС уведомляет приложение до и после сна, что ломается и как написать приложение, которое переживает возобновление.

1. Сначала вывод

  • Сон — событие, от которого у приложения «нет права отказаться». Вас уведомляют непосредственно перед этим через WM_POWERBROADCAST (PBT_APMSUSPEND), но льготный срок — около 2 секунд, а при экстренной приостановке уведомление даже не приходит.12
  • При выходе из приостановки приходит PBT_APMRESUMEAUTOMATIC, а при возобновлении по действию пользователя приходит ещё и PBT_APMRESUMESUSPEND. Обязательную работу вроде переподключения как правило кладите на первое. Переходы в низкопотребляющий простой Modern Standby и из него, однако, не всегда совпадают с этими уведомлениями, поэтому трактуйте уведомления как подспорье.34
  • Проектируйте из допущения, что TCP-соединения, последовательные порты и дескрипторы устройств не переживают возобновление. Логика переподключения, которая восстанавливает их по уведомлению о возобновлении или по ошибке обмена, — главное событие.
  • Следите за таймерами и обработкой времени. Периодическая работа останавливается во время сна, а то, как она срабатывает сразу после возобновления, различается по API таймера и среде выполнения. Случается и «огромный скачок истёкшего времени», поэтому безопасный подход — пересобрать расписание при возобновлении.
  • На интервалы, которые вы не хотите проспать, подавляйте сон явно. Используйте SetThreadExecutionState (ES_SYSTEM_REQUIRED) или запрос питания (PowerSetRequest) и всегда снимайте его, когда работа закончена.45
  • На машине с Modern Standby система во время сна всё ещё работает прерывисто, но десктопные приложения приостановлены. Нельзя держать ожидание, что «наше приложение должно продолжать работать во время сна».6
  • Стандартные инструменты расследования — powercfg (/requests, /lastwake, /sleepstudy) и Kernel-Power в журнале событий.

2. Что происходит вокруг сна — поток событий питания

ОС рассылает изменения состояния питания каждому приложению сообщением WM_POWERBROADCAST.2 Есть три главных события, которые связаны со сном и возобновлением.

Событие Смысл
PBT_APMSUSPEND Сейчас войдёт в сон (последний шанс подготовиться)
PBT_APMRESUMEAUTOMATIC Возобновилось (всегда приходит при возобновлении)
PBT_APMRESUMESUSPEND Возобновление вызвано действием пользователя (это условно)

PBT_APMSUSPEND — уведомление непосредственно перед сном, и здесь можно подготовиться, закрыв файлы и сохранив состояние. Есть, однако, два условия. Первое: на обработку даётся около 2 секунд на приложение, и если превысить это время, система идёт дальше, не дожидаясь.1 Второе: при экстренной приостановке, например при критически низком заряде батареи, она засыпает сразу, без предварительного уведомления.2 Проект «обязательно закончить до сна» не держится. Трактуйте уведомление как шанс «сделать, если успеете», а основную работу кладите на сторону возобновления.

Сторона возобновления — два этапа. PBT_APMRESUMEAUTOMATIC приходит при выходе из перехода в приостановку. Поверх этого, если машина возобновилась из-за действия пользователя вроде кнопки питания или нажатия клавиши (или присутствие пользователя обнаружили позже), следом идёт PBT_APMRESUMESUSPEND. Наоборот, несопровождаемое возобновление для удалённого пробуждения по сети или для обслуживания доставляет только PBT_APMRESUMEAUTOMATIC.3 Эти два этапа сами по себе — подсказка, как делить работу: механическое восстановление вроде пересборки соединений делайте на PBT_APMRESUMEAUTOMATIC, а обращённые к пользователю действия вроде обновления экрана или запроса повторного входа — на PBT_APMRESUMESUSPEND.

Поток уведомлений о сне и возобновленииPBT_APMSUSPEND приходит непосредственно перед сном с льготой около 2 секунд; при возобновлении всегда приходит PBT_APMRESUMEAUTOMATIC, а PBT_APMRESUMESUSPEND следует только за возобновлением по действию пользователяПриложениеОСПриложениеОССон (код не выполняется)PBT_APMSUSPEND (около 2 секунд льготы)Сохранить состояние и закрыть соединенияPBT_APMRESUMEAUTOMATIC (приходит при возобновлении)Переподключиться и восстановить состояниеPBT_APMRESUMESUSPEND (только возобновление по действию пользователя)Обновление экрана и прочая работа для пользователя

Рис. 1: Уведомления — только «слово непосредственно перед и одно-два после возобновления». Звезда восстановления — работа на стороне возобновления.

Разница между обычным сном и экстренной приостановкойОбычный сон доставляет PBT_APMSUSPEND непосредственно перед с льготой около 2 секунд на подготовку, но экстренная приостановка вроде критической батареи останавливается без предварительного уведомления, поэтому проект, который зависит от предварительного уведомления, не держитсяОбычный сонPBT_APMSUSPEND (около 2 секунд льготы)Подготовиться, затем остановитьсяЭкстренная приостановка (критически низкий заряд)Остановиться без предварительного уведомленияПроект, который исходит из того, что уведомление придёт, не держится

Рис. 2: Экстренная приостановка приходит без предупреждения. Поэтому подготовка — «бонус, если успеете», а основная работа идёт на сторону возобновления.

Заметьте: WM_POWERBROADCAST не различает вид состояния низкого потребления (сон против гибернации).4 Правильная абстракция для приложения — трактовать это как один вид события: «остановилось и вернулось». Службы и консольные приложения без окна могут получать те же уведомления, используя RegisterSuspendResumeNotification в форме обратного вызова (DEVICE_NOTIFY_CALLBACK).7

Разделение работы по двум этапам возобновленияМеханическое восстановление вроде переподключения кладите на PBT_APMRESUMEAUTOMATIC, которое приходит при возобновлении; обращённую к пользователю работу вроде обновления экрана или запроса повторного входа — на PBT_APMRESUMESUSPEND, которое приходит только при возобновлении по действию пользователяPBT_APMRESUMEAUTOMATIC (при возобновлении)Механическое восстановлениеPBT_APMRESUMESUSPEND (возобновление по действию пользователя)Работа для пользователяПереподключить и заново открыть дескрипторыОбновление экрана и запрос повторного входа

Рис. 3: Второе не приходит при несопровождаемом возобновлении, поэтому обязательное восстановление на втором промахнётесь.

3. Modern Standby — смысл «сна» изменился

Ещё один современный факт, который нужно принять, — Modern Standby. Традиционный сон S3 был простой моделью, которая «останавливала систему целиком»; сон на машине с Modern Standby — модель в духе смартфона, в которой система продолжает работать прерывисто после того, как экран гаснет.

Что здесь важно для бизнес-приложения — десктопные приложения приостанавливает Desktop Activity Moderator (DAM) на первой стадии входа в сон.6 Сама система время от времени всё ещё работает, чтобы держать сеть и принимать уведомления, но компоненты, которым это на пользу, — те, что участвуют в этом механизме; обычный код десктопного приложения не выполняется. Поэтому с точки зрения разработчика вывод один и для Modern Standby, и для S3 — проектируйте из допущения, что ваш код во время сна не выполняется.

Разница между традиционным сном и Modern StandbyТрадиционный сон S3 останавливает систему целиком, а при Modern Standby система после выключения экрана всё ещё работает прерывисто. Десктопные приложения в обоих случаях приостанавливает DAM, поэтому код приложения не выполняетсяТрадиционный сон S3: вся система останавливаетсяКод приложения не выполняетсяModern Standby: система работает прерывистоДесктопные приложения приостанавливает DAM

Рис. 4: Модель изменилась, но для десктопного приложения вывод тот же: «во время сна работать нельзя».

Ещё одна оговорка — насколько мало можно полагаться на уведомления. При Modern Standby переходы в низкопотребляющий простой и из него не совпадают с традиционным переходом в приостановку, и соединение уже может быть разорвано, так и не дождавшись уведомления. Трактуйте уведомление о возобновлении как подспорье, а переподключение, вызванное обнаружением ошибки (глава 5), кладите на основной путь восстановления.

Ещё одно отличие — «скользкое» ощущение поведения. Достижение глубин сна идёт стадиями, и момент отключений и остановок не такой резкий, как при S3. Различие между «экран просто погас» и «уснул» пользователю тоже плохо видно, поэтому когда берёте симптом, нужно подтвердить «закрывали ли крышку» и «сколько минут оставили бездействовать».

4. Что ломается — классические симптомы

TCP-соединение мертво. Во время сна другая сторона, NAT и файрволы трактуют ваше молчание как тайм-аут и отбрасывают соединение. Хуже того, сокет на этой стороне об ошибке не знает, поэтому он падает только когда вы отправляете или принимаете после возобновления. Или ещё хуже — ожидание приёма вообще никогда не даёт ошибки (поэтому нужен keepalive). Соединения с базой данных и WebSocket имеют ту же форму.

Дескрипторы последовательного порта и USB-устройства становятся недействительными. USB-подключённое устройство при возобновлении может выглядеть так, будто его «выдернули и воткнули снова» один раз, и открытый вами дескриптор начинает возвращать ошибки. Это типичный шаблон приложения управления оборудованием, которое «получает ошибку обмена только после обеда». Проектирование переподключения также разобрано в статье о последовательной связи.

Непрерывность времени ломается. Работа по таймеру вроде «опрашивать каждые 10 секунд» во время сна не срабатывает. То, как она срабатывает сразу после возобновления (истёкшая должная работа срабатывает сразу один раз, ничего не происходит до следующего периода и так далее), различается по используемому API таймера и среде выполнения, поэтому не оставляйте обработку пропущенных тиков неявному поведению — безопасный подход — пересобрать расписание по уведомлению о возобновлении. Кроме того, расчёты истёкшего времени (разница с предыдущей меткой) внезапно становятся «на 8 часов», и средние или суждения о тайм-ауте ломаются. Запланированная работа вроде «запускать каждую ночь в 2:00» просто не выполняется, если ПК в это время спит (будите его функцией Планировщика заданий «пробуждение из сна», если нужно).

Три формы, в которых ломается непрерывность времениПериодическая работа останавливается во время сна, а срабатывание после возобновления различается по API, поэтому пересоберите расписание при возобновлении; разница с предыдущей меткой после возобновления становится огромной, поэтому поставьте защиту; запланированная работа не выполняется, если машина спит, поэтому рассмотрите пробуждение Планировщиком заданийПериодическая работа: останавливаетсяПересобрать расписаниеИстёкшее время: взрываетсяЗащитить аномальные разницыЗапланированное: так и не выполнилосьПробуждение из сна

Рис. 5: Пишите обработку таймеров и времени из допущения, что «время скачет». У каждой из трёх форм есть тип контрмеры.

Три вещи, которые ломаются через сонЧерез сон TCP-соединение отброшено по тайм-ауту другой стороной, дескриптор USB-устройства обесценивается как переподключение, а работа на истёкшем времени наблюдает огромный скачок времени. Восстанавливайте каждое переподключением, повторным открытием и защитой разницыИнтервал снаTCP: собеседник отбросилUSB или истёкшее время?USB: дескриптор недействителенИстёкшее время: скачокОбнаружить + переподключитьЗаново открыть устройствоЗащитить аномальные разницы

Рис. 6: То, что ломается, делится на три семейства — «соединения», «дескрипторы» и «непрерывность времени» — и у каждого есть устоявшийся тип восстановления.

Повторная аутентификация к общим ресурсам. Сетевые диски и VPN часто нужно заново устанавливать после возобновления, и сразу после возобновления есть «долина запуска» в несколько–несколько десятков секунд, в которой доступ падает. Безопаснее не повторять всё сразу немедленно после возобновления, а немного подождать и повторять поэтапно.

5. Строим приложения, которые переживают возобновление

Принцип один. Исходите из того, что «соединения и дескрипторы не переживают сон», и структурируйте приложение так, чтобы всегда можно было восстановиться.

Обнаруживайте возобновление и восстанавливайтесь. Когда WM_POWERBROADCAST окна верхнего уровня получает PBT_APMRESUMEAUTOMATIC, отбрасывайте удерживаемые соединения и собирайте их заново. Суть в том, чтобы не полагаться только на уведомление о возобновлении. Пропущенные уведомления и обмен, который случается до уведомления, оба реальны, поэтому всегда сочетайте это с путём «переподключаться, когда обнаружена ошибка обмена», и трактуйте уведомление о возобновлении как триггер, который лишь запускает это раньше.

// C#: funnel both the resume notification and communication errors into the same reconnect path
protected override void WndProc(ref Message m)
{
    const int WM_POWERBROADCAST = 0x0218;
    const int PBT_APMRESUMEAUTOMATIC = 0x0012;
    if (m.Msg == WM_POWERBROADCAST && (int)m.WParam == PBT_APMRESUMEAUTOMATIC)
    {
        _connectionManager.RequestReconnect();   // idempotent reconnect request
    }
    base.WndProc(ref m);
}

Саму работу переподключения делайте идемпотентной (безопасно, сколько бы раз её ни вызвали), при сбое повторяйте с экспоненциальной задержкой, а в установившемся режиме рано обнаруживайте мёртвое соединение keepalive — соберите эти три как комплект, и вы переживёте не только выход из сна, но и краткий обрыв сети или перезагрузку устройства.

Проектирование переподключения, устойчивого к возобновлениюУведомление о возобновлении, ошибка обмена и сбой keepalive все сходятся в одну идемпотентную работу переподключения, которая при сбое повторяет с экспоненциальной задержкойданетУведомление о возобновлении (PBT_APMRESUMEAUTOMATIC)Идемпотентная работа переподключенияОбнаружение ошибки обменаСбой keepaliveУдалось?Вернуться к обычной работеПовторить после экспоненциальной задержки

Рис. 7: Сосредоточьте переподключение в одном идемпотентном пути и входите на ту же дорогу из уведомления о возобновлении, обнаружения ошибки или keepalive.

Пересмотрите обработку времени. Для работы, которая использует «истёкшее время с прошлого раза», поставьте защиту, которая обесценивает интервал, когда обнаруживает аномально большую разницу (не складывайте её в среднее, не трактуйте как тайм-аут). Измерение истёкшего времени через возобновление требует держать различие между часами, которые идут во время сна (настенное время), и временем, реально потраченным на работу.

На интервалы, которые вы не хотите проспать, подавляйте сон явно. Во время работы, которую нельзя проспать — миграция данных, непрерывный обмен с устройством и так далее — можно держать систему бодрствующей через SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED) (добавьте ES_DISPLAY_REQUIRED, если хотите ещё и не гасить экран).45 Более воспитанный метод — API запросов питания (PowerCreateRequest + PowerSetRequest), к которому можно приложить строку причины, и тогда powercfg /requests покажет «кто блокирует и почему».8 Заметьте: подавление через SetThreadExecutionStateна уровне потока, и снимаете вы его с того же потока, который его поставил. Для работы, которая меняет потоки, например async/await, используйте сторону запроса питания, которой управляет дескриптор. Есть оговорки. Первая: то, что они подавляют, — автоматический сон по бездействию. Они не могут остановить явное действие пользователя вроде закрытия крышки или выбора «Сон» в меню «Пуск», поэтому даже при включённом подавлении нельзя пропускать проектирование переподключения этой главы. Вторая: на батарее на машине с Modern Standby эти запросы питания тоже отсекаются через какое-то время после истечения тайм-аута сна. Работа, которую нельзя прерывать, должна быть гарантирована питанием от сети или эксплуатацией.8 Третья: всегда снимайте, когда работа закончена. Пропущенное снятие становится новым багом: «этот ПК почему-то не засыпает».

Два средства подавления снаИспользуете ли вы удобный SetThreadExecutionState или API запросов питания, к которому можно приложить строку причины и который виден администратору через powercfg, всегда снимайте его, когда работа заканчиваетсяИнтервал работы, который нельзя проспатьSetThreadExecutionStateЗапрос питания (PowerSetRequest)Удобно — только флагиС причиной — видно в powercfgВсегда снимайте, когда работа заканчивается

Рис. 8: Для любого средства «снять, когда закончите» — абсолютное условие. Запрос питания, который может сделать причину видимой, добрее к эксплуатации.

Службы и приложения без окна получают уведомления обратным вызовом через RegisterSuspendResumeNotification (DEVICE_NOTIFY_CALLBACK).7 Если непрерывная работа — подлинное требование, корневое решение — пересмотреть проект, который держит работу резидентной на клиентском ПК, который спит, и перенести её на сторону сервера или на машину, которую эксплуатируют без сна.

6. Расследование — powercfg и журнал событий

Расследование вокруг питания хорошо обслуживают инструменты, которые поставляются с ОС.

  • Не засыпает: powercfg /requests перечисляет процессы и драйверы, которые выдали запрос питания. «Приложение забыло снять SetThreadExecutionState» тоже видно здесь.
  • Сам просыпается: powercfg /lastwake показывает самую недавнюю причину пробуждения, а powercfg /waketimers — таймеры, которые сейчас зарезервированы, чтобы будить машину.
  • Качество Modern Standby: powercfg /sleepstudy генерирует отчёт о потреблении энергии и активности на каждый интервал сна.9
  • Подтверждение временной шкалы: источник Kernel-Power в журнале событий (Система) хранит записи о входе в сон и о возобновлении. Сопоставление их с логом приложения позволяет объективно подтвердить, было ли «возобновление непосредственно перед ошибкой».
Сопоставление симптомов проблем питания с командами расследованияДля симптома «не засыпает» найдите, кто держит запрос питания, через powercfg /requests; для симптома «сам просыпается» найдите причину пробуждения через /lastwake и /waketimers; для временной шкалы используйте Kernel-Power в журнале событийНе засыпаетpowercfg /requestsСам просыпаетсяpowercfg /lastwake и /waketimersХочу подтвердить временную шкалуKernel-Power в журнале событийЗабытое подавление сна тоже видно

Рис. 9: Симптомы сопоставляются с командами расследования по трём семействам. Сначала подтвердите «только что спал?», затем разделяйте.

При обработке заявки уже первый вопрос «спал ли ПК непосредственно перед этим (закрывали ли крышку)» сильно ускоряет изоляцию.

7. Итоги

  • От сна отказаться нельзя. Предварительное уведомление (PBT_APMSUSPEND) — best-effort с льготой около 2 секунд, и в экстренном случае оно не приходит. Основной проект кладите на сторону возобновления.
  • Уведомления о возобновлении — PBT_APMRESUMEAUTOMATIC (при выходе из приостановки) + PBT_APMRESUMESUSPEND (по действию пользователя). Держите переподключение по ошибке на основном пути на случай, когда уведомление не приходит.
  • Исходите из того, что соединения и дескрипторы не переживают возобновление, и реализуйте комплект из трёх частей: идемпотентное переподключение + экспоненциальная задержка + keepalive.
  • Поставьте защиту от «аномальных разниц» на работу на истёкшем времени. Запланированную работу проектируйте из допущения, что во время сна она не выполняется.
  • На интервалы, которые нельзя проспать, подавляйте сон явно через SetThreadExecutionState или запрос питания и всегда снимайте, когда закончите.
  • Расследование — powercfg (/requests, /lastwake, /sleepstudy) и журнал событий Kernel-Power. При обработке заявки сначала спрашивайте «спал ли непосредственно перед этим».

С точки зрения приложения сон — событие, в котором «время скачет без предупреждения, соединения с окружением обрываются, а потом оно возвращается». Вплели ли вы это в проект как часть повседневности, а не как аномальную ситуацию, — вот что отделяет стабильность бизнес-приложения в эпоху ноутбука.

Похожие статьи

Смежные области консультирования

KomuraSoft LLC занимается расследованием первопричин багов вроде «обмен ломается после выхода из сна» и «соединение с устройством падает после обеда», достройкой логики переподключения и обработки событий питания к существующим приложениям и ревью проекта бизнес-приложений и ПО управления оборудованием, которые исходят из работы на ноутбуке.

Справочные ссылки

  1. Microsoft Learn, PBT_APMSUSPEND event. О том, что это событие, которое приходит непосредственно перед тем, как компьютер входит в состояние приостановки; о том, что от приложения ожидается завершение работы, нужной чтобы сохранить данные; и о том, что система даёт около 2 секунд на обработку этого уведомления, а приложение, которое продолжает сверх того, подлежит прерыванию.  2

  2. Microsoft Learn, System Power Management Events. О том, что система заранее рассылает изменения режима работы вроде сна; о том, что PBT_APMSUSPEND уведомляет перед сном по бездействию, чтобы можно было подготовиться, закрыв файлы и сохранив данные; о том, что экстренная приостановка (критическая батарея и подобное) не даёт предварительного уведомления; о том, что на обработку этого сообщения даётся максимум 2 секунды на приложение и после тайм-аута её обрывают; и о том, что при возобновлении уведомляется каждое приложение.  2 3

  3. Microsoft Learn, PBT_APMRESUMESUSPEND event. О том, что это отправляется после PBT_APMRESUMEAUTOMATIC при возобновлении по действию пользователя или когда позже обнаруживается ввод пользователя; о том, что при возобновлении от внешней причины вроде удалённого пробуждения отправляется только PBT_APMRESUMEAUTOMATIC; и о том, что от приложения ожидается повторно открыть файлы, закрытые во время сна, и подготовиться к вводу пользователя.  2

  4. Microsoft Learn, WM_POWERBROADCAST message. О том, что PBT_APMRESUMEAUTOMATIC всегда отправляется при возобновлении, а PBT_APMRESUMESUSPEND отправляется ещё и при возобновлении от ввода пользователя; о том, что это сообщение не различает вид состояния низкого потребления; о том, что подробности переходов состояния питания записываются в системный журнал событий; и о вызове SetThreadExecutionState, чтобы не дать системе войти в состояние низкого потребления.  2 3 4

  5. Microsoft Learn, SetThreadExecutionState function (winbase.h). О том, что ES_SYSTEM_REQUIRED и ES_DISPLAY_REQUIRED могут подавлять сон системы по бездействию и выключение дисплея; и о том, что непрерывное подавление объявляют через ES_CONTINUOUS и снимают вызовом одного ES_CONTINUOUS, когда закончили.  2

  6. Microsoft Learn, Prepare software for modern standby. О том, что Desktop Activity Moderator (DAM) приостанавливает десктопные приложения на первой стадии перехода в Modern Standby; и о том, что система затем стадиями переходит в фазу низкого потребления и фазу устойчивости, причём прерывисто работают только разрешённые компоненты.  2

  7. Microsoft Learn, RegisterSuspendResumeNotification function (winuser.h). О том, что это API, который регистрирует получение уведомлений о приостановке/возобновлении, и о том, что указание DEVICE_NOTIFY_CALLBACK позволяет приложению или службе без окна получать уведомление через обратный вызов в дополнение к доставке сообщения оконному дескриптору.  2

  8. Microsoft Learn, PowerSetRequest function (winbase.h). О том, что на объекте запроса питания, созданном через PowerCreateRequest, можно задать тип запроса вроде «система не спит» или «дисплей не гаснет»; о том, что можно приложить диагностическую строку причины; и о том, что непогашенные запросы питания можно перечислить через powercfg /requests.  2

  9. Microsoft Learn, Modern standby SleepStudy. О том, что отчёт, сгенерированный powercfg /sleepstudy, позволяет на каждый интервал Modern Standby осмотреть потребление энергии, активность и причину пробуждения (кнопка питания, ввод пользователя, таймер пробуждения и так далее). 

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Что на самом деле значит «Не отвечает» — как Windows решает, что приложение зависло, и как проектировать приложения, которые не зависают

«Не отвечает» в Windows — механизм, в котором ОС судит, что окно не извлекало сообщение 5 секунд, и подменяет его окном-призраком. Статья...

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

Статья напрямую связана со следующими услугами.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Может ли приложение заранее узнать о сне и отказаться от него?
В текущем Windows уведомление получить можно, а отказаться нельзя. Непосредственно перед сном сообщение WM_POWERBROADCAST доставляет событие PBT_APMSUSPEND, и здесь можно подготовиться — закрыть файлы и сохранить состояние, — но на обработку даётся около 2 секунд на приложение, и если превысить это время, система идёт дальше, не дожидаясь. При экстренной приостановке, например при критически низком заряде батареи, предварительное уведомление само по себе не приходит. Поэтому проект «обязательно закончить до сна» не держится; нужен проект, который умеет восстановиться при возобновлении, когда бы ни произошёл обрыв. На отрезок работы, который вы действительно не хотите проспать, подавляйте сон явно через SetThreadExecutionState или запрос питания (PowerSetRequest).
Как обнаружить, что машина возобновила работу?
Если у приложения есть окно, обрабатывайте WM_POWERBROADCAST. При выходе из приостановки приходит PBT_APMRESUMEAUTOMATIC, а если возобновление вызвано действием пользователя (кнопка питания или нажатие клавиши), следом идёт PBT_APMRESUMESUSPEND. Несопровождаемое возобновление, которое сразу снова уходит в сон, доставляет только PBT_APMRESUMEAUTOMATIC, поэтому базовое разделение такое: обязательную работу вроде переподключения кладите на сторону PBT_APMRESUMEAUTOMATIC, а обращённую к пользователю — обновление экрана и тому подобное — на сторону PBT_APMRESUMESUSPEND. Службы и консольные приложения без окна могут получать те же уведомления через обратный вызов, используя RegisterSuspendResumeNotification с DEVICE_NOTIFY_CALLBACK.
Можно ли держать приложение работающим во время сна?
Как правило, нет. Во время сна само выполнение на CPU останавливается (на машине с Modern Standby десктопные приложения приостанавливает Desktop Activity Moderator), и код приложения не выполняется. Есть два выбора. Первый — подавлять сон только пока идёт работа. Указание ES_SYSTEM_REQUIRED через SetThreadExecutionState или выдача запроса питания через PowerCreateRequest/PowerSetRequest подавляет автоматический сон по бездействию на этот интервал (подтвердить можно через powercfg /requests). Это всё равно не остановит явное действие сна, например закрытие крышки пользователем, поэтому к возобновлению нужно быть готовым даже при включённом подавлении. Второй — принять сон и проектировать так, чтобы «наверстать после возобновления». Для запланированной работы вроде ночного пакета можно ещё будить ПК функцией Планировщика заданий «Выводить компьютер из спящего режима для выполнения задачи». Работа, которой действительно нужно идти непрерывно, принадлежит серверу или службе, настроенной так, чтобы не спать.
Почему после возобновления перестают работать TCP-соединения и последовательные порты?
Потому что сетевые адаптеры и USB-устройства тоже уходят в состояние низкого потребления во время сна. TCP-соединение уже отброшено другой стороной или по тайм-ауту NAT либо файрвола, и отправка/приём после возобновления возвращает ошибку (часто вы замечаете это только когда она случается). USB-последовательные адаптеры и подобные при возобновлении иногда трактуются как извлечение и повторная вставка устройства, и открытый вами дескриптор становится недействительным. Для обоих правильное допущение — «дескрипторы и соединения не переживают возобновление», а правильный ответ — реализовать логику переподключения, которая восстанавливает соединение по уведомлению о возобновлении или по ошибке обмена. Сочетание периодического keepalive с повторами, которые при сбое используют экспоненциальную задержку, — устоявшийся шаблон.
Как расследовать неожиданный сон или неожиданное возобновление?
Первый инструмент — команда powercfg. В направлении «не засыпает» powercfg /requests перечисляет, какие процессы и драйверы выдали запрос питания, блокирующий сон. В направлении «сам просыпается» powercfg /lastwake показывает самую недавнюю причину пробуждения, а powercfg /waketimers — таймеры, которые сейчас зарезервированы, чтобы будить машину. На машине с Modern Standby powercfg /sleepstudy даёт отчёт о потреблении и активности во время сна. История сна и возобновления также записывается в журнал событий (источник Kernel-Power в журнале Система), так что на временной шкале можно подтвердить «когда уснул и когда и почему проснулся».

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог