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