В процессе разработки интерактивных помощников, мастеров настройки или обработок первоначального заполнения базы данных разработчики часто сталкиваются с необходимостью полностью ограничить действия пользователя на время выполнения тяжелых операций. Для этого открывают форму в режиме блокировки всего интерфейса (устанавливая свойство РежимОткрытияОкна в значение РежимОткрытияОкнаФормы.БлокироватьВесьИнтерфейс).
Однако при попытке совместить блокировку интерфейса с классическим механизмом отложенного запуска через ПодключитьОбработчикОжидания платформа «1С:Предприятие 8» может уйти в глубокое зависание, требующее аварийного завершения процесса через диспетчер задач. Разберем по шагам, почему возникает эта проблема, как устроена отрисовка интерфейса в 1С и как правильно реализовать бесконфликтный запуск тяжелых процедур.
Чтобы понять природу зависания, проанализируем, что происходит на стороне операционной системы и платформы 1С в момент переключения вкладок формы:
Попытка решить эту проблему «в лоб» через глобальный метод ПодключитьОбработчикОжидания усугубляет ситуацию. Если используется глобальный контекст, платформа пытается запустить процедуру модуля управляемого приложения во время активного модального или блокирующего интерфейс окна. Это приводит к внутреннему конфликту блокировок потоков (deadlock), и клиент «1С» зависает намертво.
Кроме того, согласно официальной документации, во время временной блокировки, вызванной методом ЗаблокироватьРаботуПользователя, глобальные обработчики ожидания игнорируются или не могут быть корректно обработаны. Но как же тогда реализовать корректное поведение программы?
Согласно стандартам разработки фирмы «1С», любые длительные операции (запись больших массивов данных, заполнение базы, проведение документов) должны выполняться асинхронно в фоновом режиме. Это позволяет полностью освободить интерфейсный поток клиента для перерисовки графики и взаимодействия с пользователем.
Рассмотрим детальный алгоритм реализации этого механизма:
Шаг 1. Перенос логики заполнения на сервер. Вся процедура записи данных должна быть оформлена как экспортный метод серверного общего модуля. Метод не должен напрямую обращаться к элементам формы.
Шаг 2. Запуск фонового процесса. При переходе на вкладку чек-листа мы запускаем фоновое задание. Для этого рекомендуется использовать стандартные функции Библиотеки Стандартных Подсистем (БСП), например, ДлительныеОперации.ВыполнитьВФоне, либо встроенный механизм платформы:
// Код на клиенте в модуле формы
&НаКлиенте
Процедура ПослеПереходаНаВкладкуЧекЛиста()
// Показываем пользователю, что процесс пошел
Элементы.ПанельШагов.ТекущаяСтраница = Элементы.СтраницаЧекЛист;
// Запускаем фоновое задание на сервере
ПараметрыЗапуска = ЗапуститьЗаполнениеБазыНаСервере();
// Подключаем ЛОКАЛЬНЫЙ обработчик ожидания формы для отслеживания состояния
// Интервал опроса можно динамически увеличивать
ЭтаФорма.ПодключитьОбработчикОжидания("Подключаемый_ПроверитьВыполнениеЗадания", 1, Истина);
КонецПроцедуры
Обратите внимание: мы используем именно локальный метод формы ЭтаФорма.ПодключитьОбработчикОжидания, а не глобальный контекст. Это ключевой момент, предотвращающий взаимную блокировку потоков.
Шаг 3. Опрос состояния фонового задания. Каждую секунду (или с увеличивающимся интервалом) локальный обработчик формы делает быстрый, неблокирующий вызов сервера, чтобы проверить, завершилась ли операция. Интерфейс при этом остается отзывчивым, анимация прогресс-бара работает идеально:
&НаКлиенте
Процедура Подключаемый_ПроверитьВыполнениеЗадания()
СостояниеЗадания = ПроверитьСостояниеЗаданияНаСервере(ПараметрыЗапуска.ИдентификаторЗадания);
Если СостояниеЗадания.Статус = "Выполняется" Тогда
// Обновляем чек-лист на форме, считывая промежуточные сообщения
ОбновитьОтображениеПрогресса(СостояниеЗадания.Сообщения);
// Повторно подключаем однократный обработчик ожидания
ЭтаФорма.ПодключитьОбработчикОжидания("Подключаемый_ПроверитьВыполнениеЗадания", 1, Истина);
ИначеЕсли СостояниеЗадания.Статус = "Выполнено" Тогда
// Переходим к следующему шагу или разблокируем интерфейс
ЗавершитьЗаполнение();
Иначе
// Обработка ошибок выполнения фонового задания
ВывестиСообщениеОбОшибке(СостояниеЗадания.ИнформацияОбОшибке);
КонецЕсли;
КонецПроцедуры
Если специфика конфигурации или файловый режим работы базы данных накладывают жесткие ограничения на использование фонового выполнения, можно использовать хитрость с минимальным квантом времени ожидания. Этот способ позволяет гарантированно отрисовать форму перед тем, как клиент уйдет в тяжелый синхронный серверный вызов.
Идея заключается в использовании однократного локального обработчика ожидания формы с интервалом 0.1 секунды. Разберем этот прием подробнее:
&НаКлиенте
Процедура ПриСменеСтраницыПанели(Элемент)
Если Элементы.ПанельШагов.ТекущаяСтраница = Элементы.СтраницаЧекЛист Тогда
// Интерфейс мгновенно переключает вкладку визуально.
// Даем платформе 100 миллисекунд на полную прорисовку всех рамок, таблиц и надписей.
ЭтаФорма.ПодключитьОбработчикОжидания("Подключаемый_ЗапуститьТяжелуюЗаписьСинхронно", 0.1, Истина);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура Подключаемый_ЗапуститьТяжелуюЗаписьСинхронно()
// В этот момент пользователь уже видит красивый отрисованный чек-лист на экране.
// Теперь можно уходить в синхронную серверную транзакцию.
ВыполнитьЗаписьДанныхНаСервере();
// После возврата с сервера обновляем состояние чек-листа
ОбновитьСостояниеЧекЛистаПослеЗаписи();
КонецПроцедуры
Почему это работает? Задержка в 0.1 секунды незаметна для человеческого глаза, но для операционной системы и графического движка платформы это огромный промежуток времени. За этот квант времени поток интерфейса успевает полностью обработать сообщения отрисовки из системной очереди. Пользователь видит корректно сформированное окно чек-листа, и последующее ожидание ответа сервера воспринимается им естественно.
Важно помнить: данный способ является компромиссным. Во время выполнения процедуры ВыполнитьЗаписьДанныхНаСервере интерфейс все равно будет временно «заморожен». Для обеспечения максимального качества программного продукта всегда отдавайте приоритет Решению №1 (Фоновые задания).