При разработке в среде 1С:Предприятие часто возникает классическая архитектурная задача: необходимо обработать большой массив данных на сервере (например, загрузить файлы, пересчитать итоги или выполнить сложную выборку), сохранив при этом отзывчивость клиентского интерфейса. Если запустить тяжелый цикл непосредственно в обработчике кнопки, клиентское приложение "замрет" до завершения выполнения кода, а пользователь увидит пугающую надпись "Программа не отвечает".
Разберем, как правильно организовать взаимодействие между клиентом и сервером, чтобы пользователь видел актуальный процент выполнения и текстовые пояснения о ходе процесса. Проанализируем основные подходы: от использования Библиотеки Стандартных Подсистем (БСП) до прямой работы с фоновыми заданиями.
Выясним причину проблемы. В управляемом приложении 1С действует строгий принцип разделения контекстов. Когда клиент вызывает серверный метод, управление передается на сервер, и клиентский поток исполнения блокируется. Пока серверный метод не вернет управление (команду Возврат или окончание процедуры), клиент не сможет перерисовать форму. Даже если вы создадите реквизит ПроцентВыполнения и будете менять его в цикле на сервере, пользователь увидит изменения только тогда, когда процесс уже завершится.
Для решения этой задачи нам необходимо запустить серверный код в отдельном потоке (фоновом задании), а в клиентском сеансе настроить периодический "опрос" состояния этого задания.
Наиболее современным и надежным способом является использование подсистемы ДлительныеОперации из состава БСП. Рассмотрим алгоритм реализации по шагам.
Шаг 1. Создание серверного метода. Мы должны вынести логику обработки в экспортную процедуру общего модуля (модуль должен иметь флаг "Вызов сервера"). В этой процедуре мы будем использовать метод для передачи прогресса.
Процедура ЗагрузитьДанныеНаСервере(ПараметрыЗагрузки, АдресРезультата) Экспорт
МассивФайлов = ПараметрыЗагрузки.МассивФайлов;
ВсегоФайлов = МассивФайлов.Количество();
Для Индекс = 0 По ВсегоФайлов - 1 Цикл
// Эмуляция обработки файла
ТекущийФайл = МассивФайлов[Индекс];
ОбработатьОдинФайл(ТекущийФайл);
// Сообщаем прогресс
Процент = Целое((Индекс + 1) / ВсегоФайлов * 100);
ТекстСообщения = "Обработка файла: " + ТекущийФайл.Имя;
ДлительныеОперации.СообщитьПрогресс(Процент, ТекстСообщения);
КонецЦикла;
// Если нужно вернуть результат, помещаем его во временное хранилище
РезультатТЗ = СформироватьИтоговуюТаблицу();
ПоместитьВоВременноеХранилище(РезультатТЗ, АдресРезультата);
КонецПроцедуры
Шаг 2. Запуск на клиенте. На форме обработки мы вызываем метод БСП, который возьмет на себя создание фонового задания и открытие формы ожидания.
&НаКлиенте
Процедура ВыполнитьЗагрузку(Команда)
ПараметрыВыполнения = ДлительныеОперацииКлиент.ПараметрыВыполнения(ЭтотОбъект);
ПараметрыВыполнения.ТекстСообщения = "Пожалуйста, подождите. Идет загрузка данных...";
ПараметрыВыполнения.ОповещениеОЗавершении = Новый ОповещениеЗавершения("ЗагрузкаЗавершена", ЭтотОбъект);
// Формируем параметры для нашей серверной процедуры
ПараметрыПроцедуры = Новый Структура;
ПараметрыПроцедуры.Вставить("МассивФайлов", ПолучитьСписокФайлов());
Операция = ДлительныеОперацииКлиент.ВыполнитьВФоне(
"МойОбщийМодуль.ЗагрузитьДанныеНаСервере",
ПараметрыПроцедуры,
ПараметрыВыполнения
);
КонецПроцедуры
Шаг 3. Обработка завершения. После того как фоновое задание закончит работу, сработает процедура оповещения.
&НаКлиенте
Процедура ЗагрузкаЗавершена(Результат, ДополнительныеПараметры) Экспорт
Если Результат = Неопределено Тогда
Возврат; // Пользователь отменил операцию
КонецЕсли;
Если Результат.Статус = "Выполнено" Тогда
// Получаем данные из хранилища
ТЗ = ПолучитьИзВременногоХранилища(Результат.АдресРезультата);
ПоказатьОповещениеПользователя("Успех", , "Загрузка завершена успешно!");
ИначеЕсли Результат.Статус = "Ошибка" Тогда
ВызватьИсключение Результат.ПодробноеПредставлениеОшибки;
КонецЕсли;
КонецПроцедуры
Если ваша конфигурация не содержит БСП, нам придется реализовать механизм вручную через ФоновыеЗадания и ПодключитьОбработчикОжидания. Разберем, как это работает.
Проанализируем ситуацию: нам нужно запустить процедуру на сервере и периодически "спрашивать" сервер из клиента: "Ну что там?". Для передачи информации о прогрессе удобнее всего использовать механизм сообщений пользователю.
1. Запуск фонового задания. При нажатии кнопки на форме мы создаем уникальный идентификатор и запускаем фоновое задание через объект ФоновыеЗадания.
&НаКлиенте
Процедура СтартВручную(Команда)
ИдентификаторЗадания = ЗапуститьЗаданиеНаСервере();
// Подключаем опрос состояния каждые 2 секунды
ПодключитьОбработчикОжидания("ПроверитьСтатусЗадания", 2);
КонецПроцедуры
2. Передача прогресса через сообщения. В серверном коде мы используем метод ОбщегоНазначенияКлиентСервер.СообщитьПользователю (или системный Сообщить), упаковывая данные о прогрессе в строку специального формата.
// В серверной процедуре фонового задания
Процент = 50;
ТекстПрогресса = "{""Процент"":" + Процент + ",""Текст"":""Обработано полпути""}";
Сообщить(ТекстПрогресса);
3. Чтение прогресса на клиенте. В обработчике ожидания мы получаем список сообщений, которые "накопило" фоновое задание.
&НаКлиенте
Процедура ПроверитьСтатусЗадания()
Статус = ПолучитьСтатусИСообщения(ИдентификаторЗадания);
// Обновляем индикатор на форме
Элементы.ИндикаторПрогресса.Значение = Статус.Процент;
Состояние(Статус.Текст);
Если Статус.Завершено Тогда
ОтключитьОбработчикОжидания("ПроверитьСтатусЗадания");
ПоказатьОповещениеПользователя("Готово!");
КонецЕсли;
КонецПроцедуры
Разберем ошибки, с которыми столкнулся автор темы, чтобы не повторять их:
Ожидается выражение. Проверьте раскладку клавиатуры и настройки автозамены в текстовых редакторах.Таблицу Значений напрямую с сервера на клиент. Помните: ТаблицаЗначений — это серверный тип. Для передачи на клиент используйте либо Массив структур, либо ПоместитьВоВременноеХранилище.Недопустимое значение параметра (номер '2') возникает, когда в качестве второго параметра передается строка, не совпадающая с именем реквизита на форме, или если тип данных реквизита не соответствует передаваемому значению.ПодключитьОбработчикОжидания работает только в контексте клиента. Попытка вызвать этот метод внутри серверной процедуры (даже если она вызвана с клиента) приведет к ошибке.Рассмотрим важные нюансы производительности:
ФоновыеЗадания.ТекущееФоновоеЗадание().ПолучитьСостояние(). Если пользователь нажал "Отмена", задание должно корректно завершиться.Таким образом, мы выяснили, что для реализации качественного индикатора прогресса необходимо разделять логику на фоновое выполнение и клиентский опрос. Использование БСП значительно сокращает объем кода и позволяет использовать проверенные временем стандартные формы ожидания, что делает интерфейс вашего решения профессиональным и понятным пользователю.