ПередЗавершениемРаботыСистемы в управляемом приложении 1С выполняется на клиенте, до закрытия главного окна, и с платформы 8.3.8 в нём запрещены серверные вызовы и открытие окон. Параметр Отказ = Истина не отменяет выход окончательно: он лишь заставляет платформу показать стандартный диалог с текстом из ТекстПредупреждения, и если пользователь нажмёт «Завершить работу», приложение закроется. Поэтому любые важные действия (запись в базу, учёт выхода пользователя) нельзя завязывать на обработчики завершения — их нужно переносить на сервер.
Ниже разберу, как сейчас устроены оба обработчика выхода, что в них допустимо, и покажу рабочую схему учёта сеансов пользователей через регламентное задание.
Почему поменялось поведение при закрытии
До 8.3.8 многие конфигурации в ПередЗавершениемРаботыСистемы задавали вопросы через модальные окна и писали в базу «пользователь вышел». С переходом на асинхронную модель и ростом доли веб-клиента это стало ненадёжным: браузер может закрыть вкладку, не дожидаясь ответа сервера, а модальные диалоги в браузере работают иначе, чем в тонком клиенте.
Фирма «1С» сделала поведение однозначным: процесс завершения работы — только клиентский и только без окон. Старые конфигурации, работающие в режиме совместимости с более ранними версиями, могут вести себя по-прежнему, поэтому при снятии режима совместимости обработчики выхода стоит проверить в первую очередь.
Как работает ПередЗавершениемРаботыСистемы
Сигнатура в модуле управляемого приложения:
Процедура ПередЗавершениемРаботыСистемы(Отказ, ТекстПредупреждения)
// ...
КонецПроцедурыЛогика платформы:
| Что сделали в обработчике | Что увидит пользователь |
|---|---|
Ничего, Отказ остался Ложь | Приложение закрывается без вопросов |
Отказ = Истина, текст предупреждения заполнен | Платформенный диалог с вашим текстом и кнопками «Завершить работу» / «Продолжить работу» |
Отказ = Истина, текст пустой | Тот же диалог со стандартной формулировкой |

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

Структура регистра
Регистр сведений СеансыПользователей, непериодический, запись независимая:
| Вид | Имя | Тип |
|---|---|---|
| Измерение | НомерСеанса | Число (10, 0) |
| Измерение | НачалоСеанса | Дата (дата и время) |
| Ресурс | ИмяПользователя | Строка (100) |
| Ресурс | ИмяКомпьютера | Строка (100) |
| Ресурс | Приложение | Строка (100) |
| Ресурс | ОкончаниеСеанса | Дата (дата и время) |
Пара «номер + начало» уникальна: номера сеансов платформа со временем переиспользует, а время начала различает их.
Код регламентного задания
// Обработчик регламентного задания «Учёт сеансов пользователей».
// Регистрирует новые сеансы и проставляет окончание тем, которых больше нет.
Процедура ОбновитьЖурналСеансов() Экспорт
УстановитьПривилегированныйРежим(Истина);
ТекущийМомент = ТекущаяДатаСеанса();
АктивныеСеансы = Новый Соответствие;
Для Каждого Сеанс Из ПолучитьСеансыИнформационнойБазы() Цикл
// Фоновые задания, в том числе само это задание, не учитываем.
Если Сеанс.ПолучитьФоновоеЗадание() <> Неопределено Тогда
Продолжить;
КонецЕсли;
Ключ = КлючСеанса(Сеанс.НомерСеанса, Сеанс.НачалоСеанса);
АктивныеСеансы.Вставить(Ключ, Истина);
ЗарегистрироватьСеансЕслиНовый(Сеанс);
КонецЦикла;
ЗакрытьЗавершенныеСеансы(АктивныеСеансы, ТекущийМомент);
КонецПроцедуры
Функция КлючСеанса(НомерСеанса, НачалоСеанса)
Возврат Формат(НомерСеанса, "ЧГ=0") + "|" + Формат(НачалоСеанса, "ДФ=yyyyMMddHHmmss");
КонецФункции
Процедура ЗарегистрироватьСеансЕслиНовый(Сеанс)
НаборЗаписей = РегистрыСведений.СеансыПользователей.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.НомерСеанса.Установить(Сеанс.НомерСеанса);
НаборЗаписей.Отбор.НачалоСеанса.Установить(Сеанс.НачалоСеанса);
НаборЗаписей.Прочитать();
Если НаборЗаписей.Количество() > 0 Тогда
Возврат; // Сеанс уже в журнале.
КонецЕсли;
Запись = НаборЗаписей.Добавить();
Запись.НомерСеанса = Сеанс.НомерСеанса;
Запись.НачалоСеанса = Сеанс.НачалоСеанса;
Запись.ИмяКомпьютера = Сеанс.ИмяКомпьютера;
Запись.Приложение = ПредставлениеПриложения(Сеанс.ИмяПриложения);
Если Сеанс.Пользователь <> Неопределено Тогда
Запись.ИмяПользователя = Сеанс.Пользователь.Имя;
КонецЕсли;
НаборЗаписей.Записать();
КонецПроцедуры
Процедура ЗакрытьЗавершенныеСеансы(АктивныеСеансы, ТекущийМомент)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Сеансы.НомерСеанса КАК НомерСеанса,
| Сеансы.НачалоСеанса КАК НачалоСеанса
|ИЗ
| РегистрСведений.СеансыПользователей КАК Сеансы
|ГДЕ
| Сеансы.ОкончаниеСеанса = ДАТАВРЕМЯ(1, 1, 1)";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Если АктивныеСеансы.Получить(КлючСеанса(Выборка.НомерСеанса, Выборка.НачалоСеанса)) <> Неопределено Тогда
Продолжить; // Сеанс ещё работает.
КонецЕсли;
НаборЗаписей = РегистрыСведений.СеансыПользователей.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.НомерСеанса.Установить(Выборка.НомерСеанса);
НаборЗаписей.Отбор.НачалоСеанса.Установить(Выборка.НачалоСеанса);
НаборЗаписей.Прочитать();
Для Каждого Запись Из НаборЗаписей Цикл
Запись.ОкончаниеСеанса = ТекущийМомент;
КонецЦикла;
Попытка
НаборЗаписей.Записать();
Исключение
ЗаписьЖурналаРегистрации("УчетСеансов", УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецЦикла;
КонецПроцедурыПояснения:
- Точность окончания равна периоду задания: если оно запускается раз в 5 минут, время выхода будет определено с погрешностью до 5 минут. Для отчётов о присутствии этого хватает.
- Ошибки не глотаем молча, как часто бывает в примерах с пустым
Исключение, а пишем в журнал регистрации. - Если нужна ссылка на элемент справочника
Пользователи, в конфигурациях на БСП её можно найти поСеанс.Пользователь.УникальныйИдентификаторчерез реквизитИдентификаторПользователяИБ. - В модели сервиса с разделением данных регламентное задание и доступ к разделённым справочникам требуют отдельной проработки.
Такие доработки я обычно делаю расширением конфигурации, чтобы не снимать типовую с поддержки.
Как проверить обработчики выхода в своей конфигурации
Короткий порядок проверки, который я использую при аудите:
- Найти в модуле управляемого приложения процедуры
ПередЗавершениемРаботыСистемыиПриЗавершенииРаботыСистемы, а также все обработчики, которые они вызывают через общие модули. - Проверить, нет ли в цепочке вызовов серверных функций,
ОткрытьФорму,Вопрос,Предупреждениеи асинхронных методов. - Посмотреть подписки расширений: доработки часто добавляют свои обработчики выхода через аннотации.
- Закрыть программу в тонком клиенте и в веб-клиенте, нажимая обе кнопки диалога. Ошибки при закрытии видны сразу.
Если конфигурация построена на БСП, обработчики выхода там уже организованы через механизм предупреждений при завершении работы, и свои проверки правильнее встраивать в него, а не дописывать параллельный код в модуль приложения.
Типичные ошибки
- Вызов сервера из обработчиков выхода. Платформа выдаст ошибку о запрете серверных вызовов при завершении работы, пользователь увидит её при каждом закрытии.
- Вера в то, что Отказ удержит пользователя. Это предупреждение, а не блокировка.
- Модальные вопросы при выходе. Вместо них —
ТекстПредупреждения. - Журнал выхода, который пишет клиент. При обрыве связи или закрытии вкладки запись теряется.
- Пустые Попытка…Исключение. Ошибки записи пропадают бесследно, и журнал тихо перестаёт работать.
Если после перехода на новую платформу или снятия режима совместимости при закрытии программы сыплются ошибки, это повод для небольшого аудита конфигурации: такие места легко находятся поиском по модулям приложения.
Итог
- В
ПередЗавершениемРаботыСистемыиПриЗавершенииРаботыСистемынельзя вызывать сервер и открывать окна. Отказ = Истинапоказывает платформенный диалог, а окончательное решение остаётся за пользователем.- Обработчики выхода — только для коротких клиентских проверок и освобождения локальных ресурсов.
- Учёт входа и выхода пользователей надёжно делается на сервере регламентным заданием через
ПолучитьСеансыИнформационнойБазы().




