Как оптимизировать отложенную запись документов из таблицы и как работает ПодключитьОбработчикОжидания в 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке сложных интерфейсных обработок в 1С часто возникает задача: пользователь редактирует сводную таблицу, данные которой распределены по нескольким документам базы данных. Если выполнять запись объекта на сервере при изменении каждой отдельной ячейки (в событии ПриОкончанииРедактирования), интерфейс начинает сильно зависать из-за постоянных серверных вызовов, транзакций и ресурсоемких алгоритмов в процедурах ПередЗаписью и ПриЗаписи.

Рассмотрим детально, как устроен механизм ПодключитьОбработчикОжидания, насколько он ресурсозатратен, а также проанализируем наиболее эффективные архитектурные шаблоны для пакетного и отложенного сохранения данных.

Особенности и ресурсоемкость метода ПодключитьОбработчикОжидания

Разберем первый вопрос: насколько «тяжелым» для системы является подключение обработчика ожидания и что происходит при его повторном вызове.

  1. Нагрузка на клиент и сервер: Сам по себе вызов метода ПодключитьОбработчикОжидания и удержание таймера в памяти практически не расходуют системные ресурсы. Таймер работает на уровне клиентского приложения (событийной модели операционной системы или браузера) и не генерирует трафик к серверу 1С до момента своего непосредственного срабатывания. Нагрузка возникает исключительно тогда, когда наступает время выполнения назначенной клиентской процедуры.
  2. Поведение при повторном вызове: Если мы повторно вызываем ПодключитьОбработчикОжидания с тем же именем процедуры-обработчика, платформа не создает второй параллельный таймер. Происходит перезапуск таймера — старый отсчет сбрасывается, и время начинает отсчитываться заново. Это позволяет реализовать паттерн debounce (отложенный запуск после паузы в действиях пользователя).
  3. Использование параметра «Однократно»: Метод имеет третий необязательный параметр Однократно. Если нам нужно, чтобы процедура отработала ровно один раз после окончания ввода (например, через 30 секунд после последнего нажатия клавиши), этот параметр обязательно следует устанавливать в значение Истина.

Посмотрим на пример правильного подключения отложенного таймера на клиенте:


&НаКлиенте
Процедура ТаблицаДанныхПослеРедактирования(Элемент, НоваяСтрока, ОтменаРедактирования)
    // Перезапускаем таймер на 30 секунд с однократным срабатыванием
    ПодключитьОбработчикОжидания("ВыполнитьОтложеннуюЗаписьДанных", 30, Истина);
КонецПроцедуры

&НаКлиенте
Процедура ВыполнитьОтложеннуюЗаписьДанных()
    ЗаписатьИзмененныеДанныеНаСервере();
КонецПроцедуры

Подводные камни использования обработчиков ожидания и события ПриАктивизацииСтроки

Проанализируем риски, которые возникают при реализации автоматической записи по таймеру или смене строки:

  1. Зависание интерфейса при срабатывании таймера: Клиентская процедура обработчика ожидания выполняется в основном потоке интерфейса. Если в момент ее срабатывания пользователь продолжает активно набирать данные в другой колонке, внезапный синхронный вызов сервера заблокирует интерфейс на время записи документа.
  2. Непредсказуемость события ПриАктивизацииСтроки: Использовать событие ПриАктивизацииСтроки для записи документов крайне не рекомендуется. Это событие генерируется платформой не только при осознанном клике мышью, но и при скроллинге колесиком, фильтрации, сортировке и открытии формы. Серверные вызовы в этом событии приводят к критическому падению отзывчивости интерфейса.
  3. Потеря данных при закрытии вкладки или сбое: Если пользователь отредактировал 15 колонок и сразу закрыл форму (или браузер в веб-клиенте), таймер ожидания не успеет сработать, и все изменения будут потеряны без предупреждения.

Оптимальные архитектурные решения

Рассмотрим более надежные и быстрые подходы к сохранению данных из сводных таблиц обработки.

Решение 1. Пакетная запись по явной кнопке с фиксацией измененных строк

Это классический и самый надежный паттерн в корпоративных системах. Изменения не отправляются в базу данных сразу, а накапливаются в табличной части обработки.

  1. В табличную часть формы добавляется служебная колонка Изменена (тип Булево).
  2. При редактировании любой ячейки в событии ПриОкончанииРедактирования флаг Изменена для текущей строки взводится в Истина, а сама строка визуально выделяется (например, жирным шрифтом через условное оформление).
  3. На форму выносится командная кнопка «Записать изменения». По нажатию кнопки на сервер передаются только помеченные строки, где они последовательно или пакетно записываются.
  4. В событии формы ПередЗакрытием выполняется проверка: если есть строки со значением Изменена = Истина, пользователю выдается предупреждение о наличии несохраненных данных.

Решение 2. Проверка фактической модификации данных перед записью

Пользователь может дважды кликнуть на ячейку и нажать Enter, не меняя значение. В этом случае событие окончания редактирования сработает вхолостую. Чтобы исключить лишние вызовы, запоминаем состояние строки при начале редактирования:


&НаКлиенте
Перем ИсходныеДанныеСтроки;

&НаКлиенте
Процедура ТаблицаДанныхПередНачаломРедактирования(Элемент, Отказ, НоваяСтрока)
    // Сохраняем значения текущей строки до изменения
    ТекущаяСтрока = Элемент.ТекущиеДанные;
    ИсходныеДанныеСтроки = Новый Структура("Сумма, Количество, Комментарий", 
        ТекущаяСтрока.Сумма, 
        ТекущаяСтрока.Количество, 
        ТекущаяСтрока.Комментарий
    );
КонецПроцедуры

&НаКлиенте
Процедура ТаблицаДанныхПриОкончанииРедактирования(Элемент, НоваяСтрока, ОтменаРедактирования)
    Если ОтменаРедактирования Тогда
        Возврат;
    КонецЕсли;
    
    ТекущаяСтрока = Элемент.ТекущиеДанные;
    
    // Сравниваем только целевые реквизиты
    Если ТекущаяСтрока.Сумма <> ИсходныеДанныеСтроки.Сумма
        Или ТекущаяСтрока.Количество <> ИсходныеДанныеСтроки.Количество
        Или ТекущаяСтрока.Комментарий <> ИсходныеДанныеСтроки.Комментарий Тогда
        
        ТекущаяСтрока.Изменена = Истина;
    КонецЕсли;
КонецПроцедуры

Решение 3. Ускорение серверных обработчиков через ДополнительныеСвойства

Если запись документа выполняется медленно из-за тяжелых процедур в модуле объекта (пересчет всех табличных частей, создание движений по регистрам, сторонние интеграции), можно передавать специальный флаг через коллекцию ДополнительныеСвойства.

Посмотрим на пример серверного кода записи:


&НаСервере
Процедура ЗаписатьДокументБыстро(СсылкаНаДокумент, НовоеЗначение)
    ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
    
    // Устанавливаем флаг частичной записи
    ДокументОбъект.ДополнительныеСвойства.Вставить("ТолькоОбновлениеРеквизита", Истина);
    ДокументОбъект.ИмяРеквизита = НовоеЗначение;
    
    ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецПроцедуры

В модуле объекта документа в процедуре ПередЗаписью проверяем наличие этого свойства:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    Если ДополнительныеСвойства.Свойство("ТолькоОбновлениеРеквизита") 
        И ДополнительныеСвойства.ТолькоОбновлениеРеквизита Тогда
        // Пропускаем тяжелые пересчеты и валидации, не связанные с данным реквизитом
        Возврат;
    КонецЕсли;
    
    // Стандартные тяжелые алгоритмы проверки и пересчета
    ВыполнитьПолныйПересчетДокумента();
КонецПроцедуры

Решение 4. Выделение валидации в ПроверитьЗаполнение()

Чтобы ошибки заполнения документа не сваливались на пользователя неконтролируемым списком при финальной записи, все базовые проверки следует выносить из ПередЗаписью в стандартный обработчик ОбработкаПроверкиЗаполнения. В этом случае проверку корректности введенных данных конкретной строки можно вызывать программно с помощью метода ДокументОбъект.ПроверитьЗаполнение() еще до фактической записи объекта в транзакции.

Для защиты данных от одновременной перезаписи другими сеансами при отложенном сценарии рекомендуется накладывать пессимистическую блокировку методом ЗаблокироватьДанныеДляРедактирования() в момент старта редактирования ячейки и снимать ее методом РазблокироватьДанныеДляРедактирования() сразу после завершения сохранения.

← На главную