При разработке на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с ситуацией, когда выполнение длительной операции на стороне клиента (например, формирование сложного отчета в Word через COM-соединение или обработка большого массива данных) приводит к тому, что приложение перестает реагировать на действия пользователя. Интерфейс "зависает", появляется надпись "Программа не отвечает", и пользователь вынужден ждать завершения процесса, не имея возможности продолжать работу в других окнах системы.
В этой статье мы подробно разберем, почему стандартные методы ОписаниеОповещения не помогают в данном случае, и рассмотрим эффективные стратегии решения этой проблемы, основываясь на опыте разработчиков и внутренних механизмах платформы.
Для начала проанализируем, почему код, приведенный автором вопроса, не работает так, как ожидалось. Рассмотрим типичную попытку использовать асинхронность через оповещения:
&НаКлиенте
Процедура ЗапускАсинхроннойПроцедуры(Команда)
Оповещение = Новый ОписаниеОповещения("ЗавершениеДолгойПроцедуры", ЭтотОбъект);
ОченьДолгаяПроцедураНаКлиенте(Оповещение);
КонецПроцедуры
&НаКлиенте
Процедура ОченьДолгаяПроцедураНаКлиенте(Оповещение)
// Тяжелый цикл или работа с COM
Для Сч = 1 По 1000000 Цикл
// Что-то долго делаем
КонецЦикла;
ВыполнитьОбработкуОповещения(Оповещение);
КонецПроцедуры
Проблема заключается в том, что клиентский сеанс 1С всегда выполняется в один поток. Когда вы вызываете ОченьДолгаяПроцедураНаКлиенте, управление передается внутрь этой процедуры, и пока она не завершится, основной поток занят вычислениями. Платформа просто не может перерисовать интерфейс или обработать клик мышки, так как процессорное время полностью поглощено циклом. Использование ОписаниеОповещения здесь не создает нового потока; это лишь способ организовать последовательный вызов процедур (callback), который полезен для неблокирующих методов (например, открытия окон), но бесполезен для тяжелых вычислений.
Если нам необходимо оставить выполнение задачи именно на клиенте (например, из-за использования установленного на локальной машине MS Word), мы можем применить метод дробления задачи на части. Разберем этот алгоритм по шагам.
Идея состоит в том, чтобы выполнять небольшую порцию работы (например, обработку 5 страниц документа), а затем отдавать управление платформе на короткое время, чтобы она могла обработать очередь событий интерфейса. Это делается с помощью метода ПодключитьОбработчикОжидания.
&НаКлиенте
Перем ТекущийШаг;
&НаКлиенте
Перем ВсегоШагов;
&НаКлиенте
Процедура НачатьГенерацию(Команда)
ТекущийШаг = 1;
ВсегоШагов = 100; // Например, количество страниц
// Подключаем обработчик, который сработает через 0.1 сек
ПодключитьОбработчикОжидания("ВыполнитьПорциюРаботы", 0.1, Истина);
КонецПроцедуры
&НаКлиенте
Процедура ВыполнитьПорциюРаботы() Экспорт
// Выполняем небольшую часть задачи
ОбработатьСтраницуДокумента(ТекущийШаг);
ТекущийШаг = ТекущийШаг + 1;
Если ТекущийШаг <= ВсегоШагов Тогда
// Снова подключаем обработчик для следующей порции
ПодключитьОбработчикОжидания("ВыполнитьПорциюРаботы", 0.1, Истина);
Иначе
Сообщить("Документ готов!");
КонецЕсли;
КонецПроцедуры
В паузе между вызовами ВыполнитьПорциюРаботы поток 1С освобождается, интерфейс остается отзывчивым, и пользователь может даже нажать кнопку "Отмена", если вы предусмотрите проверку соответствующего флага.
Рассмотрим более профессиональный подход. Как правильно заметили коллеги в обсуждении, нагружать клиентское приложение тяжелыми расчетами — это "мазохизм". Правильнее всего использовать фоновые задания на стороне сервера.
Выясним причину, по которой автор не хотел этого делать: отсутствие MS Office на сервере. Однако современные стандарты разработки предлагают альтернативы:
.docx являются ZIP-архивами с XML-структурой. На сервере можно программно собрать такой файл без использования библиотек Microsoft Office.ДлительныеОперации.Схема работы будет следующей:
ДлительныеОперации.ЗапуститьВыполнениеВФоне.ПодключитьОбработчикОжидания.В сообщении №18 предложен интересный "хак" с использованием модального (или неблокирующего) окна. Мы можем открыть вспомогательную форму-пустышку, внутри которой будет запущен процесс. Хотя это не создаст честной многопоточности, использование встроенных асинхронных методов открытия окон в сочетании с обработчиками ожидания позволяет визуально отделить процесс от основной работы пользователя.
&НаКлиенте
Процедура ЗапуститьПроцесс(Команда)
ПараметрыФормы = Новый Структура("ДанныеДляРаботы", МоиДанные);
Описание = Новый ОписаниеОповещения("ПослеЗавершения", ЭтотОбъект);
ОткрытьФорму("Обработка.МояОбработка.Форма.ФоновыйПрогресс", ПараметрыФормы, , , , , Описание);
КонецПроцедуры
В самой форме ФоновыйПрогресс в обработчике ПриОткрытии также следует использовать ПодключитьОбработчикОжидания, чтобы не заблокировать процесс отрисовки самой формы прогресса.
Если задача требует 100% отзывчивости и должна выполняться именно на клиенте, единственным способом добиться настоящей многопоточности является написание внешней компоненты (например, на C++ или C#). Компонента может породить отдельный системный поток ОС, выполнить там формирование файла и по окончании отправить ВнешнееСобытие в 1С. Это самый сложный, но и самый надежный технический путь.
Проанализировав ситуацию, можно сделать следующие выводы:
Асинх/Ждать в новых версиях или ОписаниеОповещения в старых) предназначена для ожидания внешних событий (ввод пользователя, ответ сервера), а не для параллельных вычислений..docx, что полностью освобождает клиента от нагрузки.Помните, что комфорт пользователя — это отсутствие "песочных часов" на экране. Даже если процесс объективно долгий, возможность продолжать работу в программе во время его выполнения делает систему значительно более дружелюбной.