Разберем ситуацию, когда стандартная функция ОбработкаПрерыванияПользователя() в 1С:Предприятии 8.3 не позволяет прервать длительные операции, особенно те, которые выполняются на сервере. Многие разработчики сталкиваются с этой проблемой, пытаясь дать пользователю возможность остановить ресурсоемкие процессы, такие как формирование объемных отчетов или сложная обработка данных. Мы проанализируем причины такого поведения и предложим надежное решение с использованием фоновых заданий.
ОбработкаПрерыванияПользователя() не работает для серверных вызовов?Функция ОбработкаПрерыванияПользователя() в 1С предназначена для реагирования на интерактивное действие пользователя – обычно нажатие комбинации клавиш Ctrl+Break. Однако ее функциональность имеет существенные ограничения, которые необходимо понимать:
ОбработкаПрерыванияПользователя() функционирует исключительно на стороне клиента. Она не может прервать выполнение кода, который в данный момент выполняется на сервере, поскольку серверный код работает в другом контексте и не реагирует на клиентские интерактивные действия, такие как нажатия клавиш. Если ваша длительная операция фактически выполняется в серверной процедуре или функции, вставка ОбработкаПрерыванияПользователя() в клиентский код до или после вызова серверной части не даст никакого эффекта прерывания серверного процесса.Нажатие() кнопки, но не сможем прервать обработчик формы ПриОткрытии(), если в нем выполняется длительная операция, не связанная с прямым действием пользователя.ОбработкаПрерыванияПользователя(). Если внутри длительного цикла выполняется одна большая операция, например, сложный запрос к базе данных, то функция не сможет прервать ее непосредственно во время выполнения запроса. Она сработает только после завершения этой операции и перехода к следующему вызову ОбработкаПрерыванияПользователя().ОбработкаПрерыванияПользователя() может работать нестабильно или вовсе не работать в терминальных сессиях. Это может быть связано с особенностями передачи нажатий клавиш или долгим "зависанием" сессии, что не позволяет корректно обработать сигнал прерывания.Рассмотрим типовой пример кода, где разработчик пытается использовать ОбработкаПрерыванияПользователя() для прерывания формирования отчета:
Пока Не ТекДень = КонецВыборки Цикл
ОбработкаПрерыванияПользователя; // Здесь пытаемся прервать
текДата=ТекДень;
СоздатьНаСервере(ТекДень,общкол); // <strong >Длительная операция на сервере!</strong>
ТекДень = ТекДень+86400;//Добавим 1 День
текДата=ТекДень;
СостояниеОбработки = Формат(ТекДень, "ДЛФ=D");
инд=инд+окр(пар);
Индикатор = инд;
ОбновитьОтображениеДанных;
ОбработкаПрерыванияПользователя; // И здесь тоже
КонецЦикла;
В этом примере, несмотря на наличие вызовов ОбработкаПрерыванияПользователя(), если процедура СоздатьНаСервере() выполняется долго, пользователь не сможет ее прервать, так как выполнение серверного кода блокирует клиентский поток, и функция прерывания просто не сможет быть вызвана до завершения серверного вызова.
Для длительных операций, особенно тех, которые выполняются на сервере, самым эффективным и рекомендуемым подходом является использование фоновых заданий (асинхронное выполнение). Этот механизм позволяет перенести ресурсоемкую обработку на сервер, не блокируя при этом клиентский интерфейс, и предоставить пользователю возможность мониторить состояние операции и при необходимости ее отменить.
Мы рассмотрим по шагам, как переделать ваш код для использования фоновых заданий, на примере формирования отчета производства за месяц.
Для того чтобы процедура или функция могла быть выполнена в фоновом задании, она должна быть размещена в общем модуле, который отмечен как &НаСервереБезКонтекста (или просто &НаСервере, если она требует контекст, но для фоновых заданий чаще используется безконтекстный вызов), и быть экспортной. Мы перенесем основную логику формирования отчета в такую серверную процедуру.
Предположим, что ваша процедура СоздатьНаСервере(ТекДень, общкол) выполняет основную работу для одного дня. Если общий отчет формируется путем цикла по дням, то логично перенести весь этот цикл в фоновое задание. Создадим новый общий модуль (например, "ДлительныеОперацииСервер") и добавим в него экспортную процедуру:
// В ОбщемМодуле "ДлительныеОперацииСервер"
&НаСервереБезКонтекста
Процедура СформироватьОтчетПроизводстваФоново(ПараметрыЗадания) Экспорт
// ПараметрыЗадания - это структура или массив с необходимыми входными данными
// Например: ПараметрыЗадания.НачалоПериода, ПараметрыЗадания.КонецПериода, ПараметрыЗадания.ОбщаяКоллекция
НачалоПериода = ПараметрыЗадания.НачалоПериода;
КонецПериода = ПараметрыЗадания.КонецПериода;
ОбщаяКоллекция = ПараметрыЗадания.ОбщаяКоллекция; // Если это ссылка на объект, она будет недоступна напрямую
// Нужно передавать идентификаторы или полные данные
ТекДень = НачалоПериода;
Пока ТекДень <= КонецПериода Цикл
// Здесь мы можем вызывать вашу существующую серверную функцию
// Если она также находится в общем модуле или доступна
СоздатьНаСервере(ТекДень, ОбщаяКоллекция);
// ВАЖНО: В фоновом задании нет прямого доступа к клиентским переменным формы.
// Состояние и индикатор нужно сохранять в хранилище значений,
// или в реквизите объекта, который мониторится клиентом.
// Пример записи прогресса для мониторинга
ФоновыеЗадания.УстановитьПрогресс(Цел((ТекДень - НачалоПериода) / (КонецПериода - НачалоПериода) * 100));
ТекДень = ТекДень + 86400; // Добавим 1 День (секунд в дне)
КонецЦикла;
// Если нужно сохранить результат работы, можно его поместить во временное хранилище
// или записать в базу данных.
// Например, СсылкаНаРезультат = ПоместитьВоВременноеХранилище(ПолученныйОтчет);
// ВозвращаемоеЗначение = СсылкаНаРезультат;
// ФоновоеЗадание.УстановитьВыполненноеЗадание(ВозвращаемоеЗначение); // Внутри фонового задания
КонецПроцедуры
// Предполагается, что ваша процедура СоздатьНаСервере() определена в этом же или доступном серверном модуле.
// Если СоздатьНаСервере() — это функция, которая возвращает что-то для каждого дня,
// то СформироватьОтчетПроизводстваФоново должна собирать эти результаты.
// Если СоздатьНаСервере() непосредственно пишет данные в базу, то изменений не требуется.
&НаСервереБезКонтекста
Процедура СоздатьНаСервере(ТекДень, ОбщКол) Экспорт
// Ваш текущий код обработки для одного дня
// ...
КонецПроцедуры
Теперь мы можем запустить эту длительную операцию с клиентской стороны, не блокируя интерфейс. Для этого используем объект ФоновыеЗадания.
// В модуле формы на клиенте
&НаКлиенте
Перем ИдентификаторФоновогоЗаданияПоОтчету ЭКСПОРТ; // Для хранения идентификатора запущенного задания
Процедура КомандаСформироватьОтчет(Команда)
// Подготовим параметры для фонового задания
ПараметрыЗадания = Новый Структура;
ПараметрыЗадания.Вставить("НачалоПериода", НачалоМесяца(ТекущаяДата())); // Пример: текущий месяц
ПараметрыЗадания.Вставить("КонецПериода", КонецМесяца(ТекущаяДата()));
// ПараметрыЗадания.Вставить("ОбщаяКоллекция", ВашаОбщаяКоллекция); // Если нужно передать ссылку на объект,
// используйте ее УникальныйИдентификатор
// или пересохраняйте данные в хранилище
// Запускаем фоновое задание
Попытка
// Важно: укажите полный путь к процедуре в общем модуле
ФоновоеЗадание = ФоновыеЗадания.Выполнить("ДлительныеОперацииСервер.СформироватьОтчетПроизводстваФоново",
ПараметрыЗадания,
"Формирование отчета производства");
ИдентификаторФоновогоЗаданияПоОтчету = ФоновоеЗадание.УникальныйИдентификатор;
// Подключаем обработчик ожидания для мониторинга статуса
ПодключитьОбработчикОжидания("ПроверитьСтатусФоновогоЗаданияОтчета", 2, Истина); // Проверять каждые 2 секунды
ПоказатьОповещениеПользователя("Формирование отчета запущено в фоновом режиме.");
// Можно открыть форму длительной операции для отображения прогресса и кнопки "Отмена"
// ДлительныеОперации.ПоказатьПрогресс(ИдентификаторФоновогоЗаданияПоОтчету, "Формирование отчета...");
Исключение
Сообщить("Ошибка при запуске фонового задания: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Пользователю необходимо видеть прогресс выполнения операции и иметь возможность ее отменить. Для этого мы будем периодически проверять состояние фонового задания с клиента с помощью обработчика ожидания.
// В модуле формы на клиенте
&НаКлиенте
Процедура ПроверитьСтатусФоновогоЗаданияОтчета()
Если ИдентификаторФоновогоЗаданияПоОтчету = Неопределено Тогда
ОтключитьОбработчикОжидания("ПроверитьСтатусФоновогоЗаданияОтчета");
Возврат;
КонецЕсли;
// Вызываем серверную функцию для получения статуса задания
РезультатПроверки = ПроверитьСтатусФоновогоЗаданияНаСервере(ИдентификаторФоновогоЗаданияПоОтчету);
Если РезультатПроверки.Выполнено Тогда
ОтключитьОбработчикОжидания("ПроверитьСтатусФоновогоЗаданияОтчета");
ПоказатьОповещениеПользователя("Отчет успешно сформирован.");
ИдентификаторФоновогоЗаданияПоОтчету = Неопределено;
// Получить и обработать результат, если фоновое задание что-то возвращало
// Например, через временное хранилище
ИначеЕсли РезультатПроверки.Выполняется Тогда
// Обновляем индикатор прогресса (если он был реализован в фоновом задании)
// Например, через Форма.Состояние или ИндикаторПрогресса
Состояние(РезультатПроверки.Прогресс + "%: " + "Формирование отчета...");
// Если используется форма длительной операции, она обновляется автоматически
ИначеЕсли РезультатПроверки.Отменено Тогда
ОтключитьОбработчикОжидания("ПроверитьСтатусФоновогоЗаданияОтчета");
ПоказатьОповещениеПользователя("Формирование отчета отменено пользователем.");
ИдентификаторФоновогоЗаданияПоОтчета = Неопределено;
ИначеЕсли ЗначениеЗаполнено(РезультатПроверки.ТекстОшибки) Тогда
ОтключитьОбработчикОжидания("ПроверитьСтатусФоновогоЗаданияОтчета");
Сообщить("Ошибка при формировании отчета: " + РезультатПроверки.ТекстОшибки, СтатусСообщения.Важное);
ИдентификаторФоновогоЗаданияПоОтчета = Неопределено;
КонецЕсли;
КонецПроцедуры
// Серверная функция для проверки статуса фонового задания
// В ОбщемМодуле "ДлительныеОперацииСервер" (или другом подходящем общем модуле)
&НаСервереБезКонтекста
Функция ПроверитьСтатусФоновогоЗаданияНаСервере(ИДЗадания) Экспорт
РезультатПроверкиФЗ = Новый Структура;
РезультатПроверкиФЗ.Вставить("Выполнено", Ложь);
РезультатПроверкиФЗ.Вставить("Выполняется", Ложь);
РезультатПроверкиФЗ.Вставить("Отменено", Ложь);
РезультатПроверкиФЗ.Вставить("ТекстОшибки", "");
РезультатПроверкиФЗ.Вставить("Прогресс", 0); // Добавим для отображения прогресса
ФоновоеЗадание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(ИДЗадания);
Если ФоновоеЗадание = Неопределено Тогда
РезультатПроверкиФЗ.ТекстОшибки = "Фоновое задание не найдено или уже завершено.";
Возврат РезультатПроверкиФЗ;
КонецЕсли;
Если ФоновоеЗадание.Состояние = СостояниеФоновогоЗадания.Завершено Тогда
РезультатПроверкиФЗ.Выполнено = Истина;
ИначеЕсли ФоновоеЗадание.Состояние = СостояниеФоновогоЗадания.Выполняется Тогда
РезультатПроверкиФЗ.Выполняется = Истина;
РезультатПроверкиФЗ.Прогресс = ФоновоеЗадание.Прогресс; // Получаем текущий прогресс
ИначеЕсли ФоновоеЗадание.Состояние = СостояниеФоновогоЗадания.ЗавершеноАварийно Тогда
РезультатПроверкиФЗ.ТекстОшибки = КраткоеПредставлениеОшибки(ФоновоеЗадание.ИнформацияОбОшибке);
ИначеЕсли ФоновоеЗадание.Состояние = СостояниеФоновогоЗадания.Отменено Тогда
РезультатПроверкиФЗ.Отменено = Истина;
Иначе
// Другие состояния: Ожидает, Приостановлено и т.д.
РезультатПроверкиФЗ.Выполняется = Истина; // По умолчанию считаем, что еще выполняется
КонецЕсли;
Возврат РезультатПроверкиФЗ;
КонецФункции
Ключевое преимущество фоновых заданий — возможность их отмены. Мы добавим на форму кнопку "Отмена" и соответствующий обработчик.
// В модуле формы на клиенте
&НаКлиенте
Процедура КомандаОтменитьФормированиеОтчета(Команда)
Если ИдентификаторФоновогоЗаданияПоОтчету = Неопределено Тогда
Сообщить("Нет запущенных фоновых заданий для отмены.");
Возврат;
КонецЕсли;
// Вызываем серверную функцию для отмены задания
Если ОтменитьФоновоеЗаданиеНаСервере(ИдентификаторФоновогоЗаданияПоОтчета) Тогда
ПоказатьОповещениеПользователя("Запрос на отмену фонового задания отправлен.");
// Отключение обработчика ожидания произойдет при следующем цикле ПроверитьСтатусФоновогоЗаданияОтчета
Иначе
Сообщить("Не удалось отменить фоновое задание.");
КонецЕсли;
КонецПроцедуры
// Серверная функция для отмены фонового задания
// В ОбщемМодуле "ДлительныеОперацииСервер" (или другом подходящем общем модуле)
&НаСервереБезКонтекста
Функция ОтменитьФоновоеЗаданиеНаСервере(ИДЗадания) Экспорт
Попытка
ФоновоеЗадание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(ИДЗадания);
Если ФоновоеЗадание <> Неопределено Тогда
ФоновоеЗадание.Отменить();
Возврат Истина;
КонецЕсли;
Исключение
// Обработка ошибки, если задание не найдено или не может быть отменено
Сообщить("Ошибка при попытке отмены фонового задания: " + ОписаниеОшибки());
КонецПопытки;
Возврат Ложь;
КонецФункции
ПоместитьВоВременноеХранилище()) или передавайте их уникальные идентификаторы, по которым фоновое задание сможет получить данные из базы.ФоновыеЗадания.УстановитьПрогресс(Процент), как показано в примере выше. Клиент будет считывать это значение и обновлять индикатор на форме.ДлительныеОперации: Платформа 1С предлагает готовый механизм ДлительныеОперации, который упрощает отображение форм прогресса, кнопок отмены и получение результатов фоновых заданий. Если вам нужна более сложная индикация и управление, изучите этот механизм.Перевод длительной серверной операции на использование фоновых заданий — это современный и эффективный подход, который не только решает проблему с прерыванием выполнения, но и значительно улучшает пользовательский опыт, делая работу программы более отзывчивой и стабильной.