При разработке конфигураций на платформе 1С:Предприятие 8.3 программисты часто используют глобальные обработчики ожидания, зарегистрированные в модуле управляемого приложения. Такие процедуры с заданной периодичностью выполняют действия на клиенте или опрашивают сервер. Однако на практике возникает неприятная коллизия: пользователь закрывает приложение, и в этот же момент срабатывает таймер ожидания. Платформа переходит в режим закрытия сеанса, в котором серверные вызовы блокируются на системном уровне, что приводит к возникновению критической ошибки завершения работы.
Проанализируем природу этого поведения и подробно рассмотрим надежные способы обхода данной проблемы.
Начиная с версии платформы 8.3.8 при работе без режима совместимости внедрена асинхронная модель закрытия приложения. На уровне ядра 1С действует строгое правило: во время сценария завершения работы (включая цепочку событий модуля приложения ПередЗавершениемРаботыСистемы и ПриЗавершенииРаботыСистемы) любые серверные вызовы — как явные процедуры с директивами &НаСервере или &НаСервереБезКонтекста, так и неявные обращения к серверным данным — жестко блокируются. Поскольку платформа не снимает глобальные обработчики ожидания автоматически в момент нажатия пользователем кнопки закрытия, процедура таймера пытается выполнить сетевой запрос к серверу и вызывает исключение.
Рассмотрим решение, предложенное в обсуждении. Мы объявляем переменную на уровне клиентского модуля приложения и взводим ее при начале процедуры закрытия сеанса.
Разберем по шагам, как реализовать этот подход в модуле управляемого приложения:
ПередНачаломЗавершенияРаботы или ПередЗавершениемРаботыСистемы устанавливаем флаг в значение Истина.Посмотрим на пример программного кода в модуле управляемого приложения:
Перем СерверныеВызовыЗапрещены Экспорт;
Процедура ПриНачалеРаботыСистемы()
СерверныеВызовыЗапрещены = Ложь;
ПодключитьОбработчикОжидания("ПериодическаяПроверкаСервера", 60);
КонецПроцедуры
Процедура ПериодическаяПроверкаСервера() Экспорт
Если СерверныеВызовыЗапрещены Тогда
Возврат;
КонецЕсли;
ВыполнитьДействиеНаСервере();
КонецПроцедуры
Процедура ПередЗавершениемРаботыСистемы(Отказ, Предупреждения)
СерверныеВызовыЗапрещены = Истина;
КонецПроцедуры
Обратите внимание: если завершение работы программы может быть отменено пользователем (например, при ответе «Нет» в интерактивном диалоге подтверждения выхода) или программно через параметр Отказ = Истина, флаг останется во взведенном состоянии, и обработчик больше не будет выполнять полезную работу в текущем сеансе. В таком случае необходимо предусмотреть сброс флага обратно в Ложь.
Рассмотрим более чистое архитектурное решение. Вместо холостых срабатываний процедуры с проверкой булевой переменной мы можем полностью снять процедуру с таймера платформы с помощью метода ОтключитьОбработчикОжидания.
Разберем реализацию этого метода в модуле управляемого приложения:
Процедура ПриЗавершенииРаботыСистемы()
ОтключитьОбработчикОжидания("ПериодическаяПроверкаСервера");
КонецПроцедуры
Вызов метода ОтключитьОбработчикОжидания гарантирует, что платформа прекратит отсчет интервала и больше не вызовет клиентский код обработчика в цикле ожидания.
Выясним еще один надежный вариант защиты — локализацию серверного вызова внутри конструкции Попытка ... Исключение. Ошибка запрета серверных вызовов при выходе из системы является стандартным перехватываемым исключением платформы.
Посмотрим на пример безопасного вызова внутри обработчика:
Процедура ПериодическаяПроверкаСервера() Экспорт
Попытка
ВыполнитьДействиеНаСервере();
Исключение
// Подавляем системную ошибку запрета серверных вызовов при закрытии сеанса
КонецПопытки;
КонецПроцедуры
Этот способ можно комбинировать с первыми двумя. Он страхует систему от непредвиденных падений интерфейса даже в том случае, если момент наступления события таймера совпал с закрытием с точностью до миллисекунды.
Проанализируем структуру решения в целом. Регулярный опрос сервера с клиента (polling) создает избыточную нагрузку на кластер серверов и сеть. Чтобы исключить подобные проблемы, рекомендуется придерживаться следующих правил:
ПередЗакрытием и ПриЗакрытии доступен стандартный параметр ЗавершениеРаботы, по которому можно точно отследить закрытие приложения, а сам таймер формы автоматически уничтожается вместе с ней.