Все ниже описанное касается управляемого приложения.
Начиная с версии 8.3.8 фирма 1С изменила подход к завершению работы и закрытию приложения. Новомодные веяния – ли, ошибки при работе с WEB интерфейсом – ли, но теперь со всех обработчиков событий, используемых нами при закрытии – «ПередЗавершениемРаботыСистемы» и «ПриЗавершенииРаботыСистемы», сервер не доступен. Рассмотрим, как теперь работают данные обработчики.
Обработчик: ПередЗавершениемРаботыСистемы(Отказ, ТекстПредупреждения)
Как мы помним возникает перед завершением программы, до закрытия главного окна. Если «Отказ» равен «Ложь», то программа просто закрывается, не выдавая никаких вопросов. В нем можно сделать всяческие проверки, все функции должны быть только клиентские (В процессе завершения работы приложения запрещены серверные вызовы и открытие окон.) и может быть установлен отказ от выхода из программы. И вот тут появляется первый подвох. Установление «Отказ» в Истина не отменяет выход из системы, а только указывает, что нужно выдать платформенный диалог, в котором будет предложено выйти или остаться с текстом из параметра «ТекстПредупреждения»:

И если пользователь нажмет «Завершить работу», все ваши проверки идут лесом. Платформа просто закрывает приложение не зависимо ни от чего. Отловить этот момент в коде нельзя, это все происходит на уровне платформы.
Если же пользователь нажмет на «Продолжить работу», то возможны два варианта событий:
- Если вы внутри вызова функций из данной процедуры подключали обработчик ожидания
ПодключитьОбработчикОжидания("ИмяФункцииОбработчикаОжидания", 0.1, Истина);тогда программа перейдет на этот обработчик, и выполнятся все действия которые вы в нем задали, даже вызов серверных функций.
- Если же вы этого не делали, программа просто продолжит работу.
Ответ на первый глобальный русский вопрос «кто виноват?» понятен. Я постараюсь ответить на второй «что делать?». Если нам надо что то зафиксировать в базе, например информацию о работе пользователей в системе. Кто когда заходил, кто когда вышел.
Решение однако лежит вообще в другой плоскости, а именно, в регламентном задании которое периодически будет опрашивать систему на активных пользователей, и фиксировать что такой то пользователь уже отсутствует в списке активных.
Для опроса системы у нас есть функция ПолучитьСеансыИнформационнойБазы(), которая возвращает массив сеансов. А для фиксации, что пользователя в сеансах уже нет нужно создавать новый регистр. Я предлагаю периодический (в пределах секунды) РС с двумя измерениями «Пользователь» и «НомерСеанса», в ресурсах же должно быть как минимум поле «ЗавершениеСеанса» по которому мы будем понимать что пользователь нами не обработан или обработан но активен.

Процедура ОбработатьСеансыАктивныхПользователей() Экспорт
ТекущаяДатаСеанса = ТекущаяДатаСеанса();
ТабАктивныхПользователей = Новый ТаблицаЗначений;
ТабАктивныхПользователей.Колонки.Добавить("Пользователь", Новый ОписаниеТипов("СправочникСсылка.Пользователи"));
ТабАктивныхПользователей.Колонки.Добавить("НомерСеанса", Новый ОписаниеТипов("Число"));
СеансыИнформационнойБазы = ПолучитьСеансыИнформационнойБазы();
Для Каждого СеансИБ Из СеансыИнформационнойБазы Цикл
//Фоновые задания или неавторизованных пользователей пропускаем
Если СеансИБ.ПолучитьФоновоеЗадание() <> Неопределено ИЛИ (ТипЗнч(СеансИБ.Пользователь) = Тип("ПользовательИнформационнойБазы") И НЕ ЗначениеЗаполнено(СеансИБ.Пользователь.Имя)) Тогда
Продолжить;
КонецЕсли;
АктивныйПользователь = ДобавитьЗаписьАвторизацииПользователей(СеансИБ, ТекущаяДатаСеанса);
Если ЗначениеЗаполнено(АктивныйПользователь) Тогда
НоваяСтрока = ТабАктивныхПользователей.Добавить();
НоваяСтрока.Пользователь = АктивныйПользователь;
НоваяСтрока.НомерСеанса = СеансИБ.НомерСеанса;
КонецЕсли;
КонецЦикла;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ккПериодическиеСведенияОПользователяхСрезПоследних.Период КАК Период,
| ккПериодическиеСведенияОПользователяхСрезПоследних.Пользователь КАК Пользователь,
| ккПериодическиеСведенияОПользователяхСрезПоследних.НомерСеанса КАК НомерСеанса
|ИЗ
| РегистрСведений.ккПериодическиеСведенияОПользователях.СрезПоследних(
| &ДатаРегистрации,
| НЕ (Пользователь, НомерСеанса) В (&ТабРаботающих)
| И ЗавершенияСеанса = ДатаВремя(1,1,1)) КАК ккПериодическиеСведенияОПользователяхСрезПоследних";
Запрос.УстановитьПараметр("ДатаРегистрации", ТекущаяДатаСеанса);
Запрос.УстановитьПараметр("ТабРаботающих", ТабАктивныхПользователей);
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
НаборЗаписей = РегистрыСведений.ккПериодическиеСведенияОПользователях.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Период.Установить(Выборка.Период);
НаборЗаписей.Отбор.Пользователь.Установить(Выборка.Пользователь);
НаборЗаписей.Отбор.НомерСеанса.Установить(Выборка.НомерСеанса);
НаборЗаписей.Прочитать();
Для каждого СведенияОПользователе Из НаборЗаписей Цикл
СведенияОПользователе.ЗавершенияСеанса = ТекущаяДатаСеанса;
СведенияОПользователе.ВремяВСеансе = СведенияОПользователе.ЗавершенияСеанса - СведенияОПользователе.Период;
КонецЦикла;
Попытка
Если НаборЗаписей.Количество() Тогда
НаборЗаписей.Записать();
КонецЕсли;
Исключение
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Функция ДобавитьЗаписьАвторизацииПользователей(СеансИБ, ТекущаяДатаСеанса) Экспорт
ТекущийПользователь = НайтиСсылкуПоИдентификаторуПользователя(СеансИБ.Пользователь.УникальныйИдентификатор);
Если НЕ ЗначениеЗаполнено(ТекущийПользователь) Тогда
Возврат Неопределено;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| 1 КАК Поле
|ИЗ
| РегистрСведений.ккПериодическиеСведенияОПользователях.СрезПоследних(
| &ДатаРегистрации,
| Пользователь = &Пользователь
| И НомерСеанса = &НомерСеанса
| И ЗавершенияСеанса = ДАТАВРЕМЯ(1, 1, 1)) КАК ккПериодическиеСведенияОПользователяхОстатки";
Запрос.УстановитьПараметр("ДатаРегистрации", ТекущаяДатаСеанса);
Запрос.УстановитьПараметр("Пользователь", ТекущийПользователь);
Запрос.УстановитьПараметр("НомерСеанса", СеансИБ.НомерСеанса);
Результат = Запрос.Выполнить();
НаборЗаписей = РегистрыСведений.ккПериодическиеСведенияОПользователях.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Период.Установить(СеансИБ.НачалоСеанса);
НаборЗаписей.Отбор.Пользователь.Установить(ТекущийПользователь);
НаборЗаписей.Отбор.НомерСеанса.Установить(СеансИБ.НомерСеанса);
НаборЗаписей.Прочитать();
Если НЕ Результат.Пустой() Тогда
Если НаборЗаписей.Количество() Тогда
СведенияОПользователе = НаборЗаписей[0];
СведенияОПользователе.ВремяВСеансе = ТекущаяДатаСеанса - СведенияОПользователе.Период;
КонецЕсли;
Иначе
СведенияОПользователе = НаборЗаписей.Добавить();
СведенияОПользователе.Период = СеансИБ.НачалоСеанса;
СведенияОПользователе.Пользователь = ТекущийПользователь;
СведенияОПользователе.НомерСеанса = СеансИБ.НомерСеанса;
СведенияОПользователе.Подключение = 1;
СведенияОПользователе.ВремяВСеансе = ТекущаяДатаСеанса - СведенияОПользователе.Период;
СведенияОПользователе.ИмяПриложения = ПредставлениеПриложения(СеансИБ.ИмяПриложения);
СведенияОПользователе.ИмяКомпьютера = СеансИБ.ИмяКомпьютера;
КонецЕсли;
Попытка
НаборЗаписей.Записать();
Исключение
КонецПопытки;
Возврат ТекущийПользователь;
КонецФункции
Функция НайтиСсылкуПоИдентификаторуПользователя(Идентификатор)
// Нет доступа к разделенному справочнику из неразделенного сеанса.
Если ОбщегоНазначения.РазделениеВключено()
И Не ОбщегоНазначения.ДоступноИспользованиеРазделенныхДанных() Тогда
Возврат Неопределено;
КонецЕсли;
Запрос = Новый Запрос;
ШаблонТекстаЗапроса =
"ВЫБРАТЬ
| Ссылка КАК Ссылка
|ИЗ
| %1
|ГДЕ
| ИдентификаторПользователяИБ = &Идентификатор";
ТекстЗапросаПоПользователям = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(ШаблонТекстаЗапроса, Метаданные.Справочники.Пользователи.ПолноеИмя());
ТекстЗапросаПоВнешнимПользователям = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(ШаблонТекстаЗапроса, Метаданные.Справочники.ВнешниеПользователи.ПолноеИмя());
Запрос.Текст = ТекстЗапросаПоПользователям;
Запрос.Параметры.Вставить("Идентификатор", Идентификатор);
Результат = Запрос.Выполнить();
Если НЕ Результат.Пустой() Тогда
Выборка = Результат.Выбрать();
Выборка.Следующий();
Возврат Выборка.Ссылка;
КонецЕсли;
Запрос.Текст = ТекстЗапросаПоВнешнимПользователям;
Результат = Запрос.Выполнить();
Если НЕ Результат.Пустой() Тогда
Выборка = Результат.Выбрать();
Выборка.Следующий();
Возврат Выборка.Ссылка;
КонецЕсли;
Возврат Справочники.Пользователи.ПустаяСсылка();
КонецФункции
Обработчик: ПриЗавершенииРаботыСистемы()
Привожу полный текст справки:
«Описание:
Возникает перед завершением работы в режиме управляемого приложения после закрытия главного окна. В данной процедуре могут быть выполнены действия, необходимые при выходе из программы.
Примечание:
В данной процедуре не допускаются:
- открытие форм и других окон,
- использование серверных вызовов,
- использование асинхронных вызовов.
Не поддерживаются:
- выдача сообщений,
- установка текста в панели состояния,
- другие действия, требующие наличия главного окна.»
Вот честно, не понимаю вообще какие действия можно осуществить в данной процедуре? Да мы можем написать кучу кода, что-то сделать с файлами, но ничего сообщить пользователю, что то сделать с базой – мы уже не можем.
Резюме
Резюмируя данную статью хочу поблагодарить фирму 1С, что не дает нам соскучиться и всегда на страже нашего свободного времени.
Все комментарии
Чтобы оставить комментарий, необходимо войти или зарегистрироваться.
Пока нет комментариев. Будьте первым!