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

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

Мы сталкиваемся с распространенной проблемой в 1С:Предприятии: длительное выполнение серверных процедур, особенно при открытии форм, приводит к блокировке пользовательского интерфейса. В данной ситуации пользователь описал проблему, когда процедура занимает до 40 секунд, что неприемлемо для комфортной работы. Мы рассмотрим, как можно решить эту проблему, используя два основных подхода: оптимизацию запросов и выполнение задач в фоновом режиме с последующим отображением результатов.

Анализ исходной проблемы и поиск узких мест

Давайте проанализируем исходный код процедуры Yoxla, предоставленный пользователем. Мы видим, что в нем присутствует несколько запросов, выполняющихся в цикле, что является классическим источником низкой производительности.


Процедура Yoxla Экспорт
    Слова = Новый Массив;
    Слова.Добавить("Bildirişlər");
    
    Запрос = Новый Запрос;
    Запрос.Текст = "ВЫБРАТЬ
    | ПоступлениеТоваровУслугТовары.Номенклатура КАК Номенклатура,
    | ПоступлениеТоваровУслугТовары.Номенклатура.Артикул КАК НоменклатураАртикул,
    | ПоступлениеТоваровУслугТовары.Автомобиль КАК Автомобиль,
    | ПоступлениеТоваровУслугТовары.Ссылка.Дата КАК Дата,
    | ПоступлениеТоваровУслугТовары.Ссылка.ОтветственныйПоставщик.Наименование КАК ОтветственныйПоставщикНаименование,
    | ПоступлениеТоваровУслугТовары.Количество КАК Количество
    |ИЗ
    | Документ.ПоступлениеТоваровУслуг.Товары КАК ПоступлениеТоваровУслугТовары
    |ГДЕ
    | ПоступлениеТоваровУслугТовары.Ссылка.Дата МЕЖДУ &Дата И &Дата2
    | И ПоступлениеТоваровУслугТовары.Ссылка.ОтветственныйПоставщик.Наименование <> ""Rəhbərlik""
    | И ПоступлениеТоваровУслугТовары.Автомобиль.Наименование <> ""Anbar""";
    
    Запрос.УстановитьПараметр("Дата", Дата('2023.12.01 00:00:00'));
    Запрос.УстановитьПараметр("Дата2", ТекущаяДата-60*60*24*2);
    
    РезультатЗапроса = Запрос.Выполнить;
    ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать;
    
    Пока ВыборкаДетальныеЗаписи.Следующий Цикл // Главный цикл обхода выборки
        Alfa = 0;
        Beta = 0;
        
        // Первый вложенный запрос в цикле
        Запрос = Новый Запрос;
        Запрос.Текст = "ВЫБРАТЬ
        | ВозвратТоваровПоставщикуТовары.Номенклатура КАК Номенклатура
        |ИЗ
        | Документ.ВозвратТоваровПоставщику.Товары КАК ВозвратТоваровПоставщикуТовары
        |ГДЕ
        | ВозвратТоваровПоставщикуТовары.Номенклатура = &Номенклатура
        | И ВозвратТоваровПоставщикуТовары.Количество = &Количество
        | И ВозвратТоваровПоставщикуТовары.Ссылка.Дата > &Дата";
        Запрос.УстановитьПараметр("Дата", ВыборкаДетальныеЗаписи.Дата);
        Запрос.УстановитьПараметр("Количество", ВыборкаДетальныеЗаписи.Количество);
        Запрос.УстановитьПараметр("Номенклатура", ВыборкаДетальныеЗаписи.Номенклатура);
        Резуль = Запрос.Выполнить.Выбрать;
        Пока Резуль.Следующий Цикл
            Alfa = 1;
        КонецЦикла;
        
        // Второй вложенный запрос в цикле
        Запрос = Новый Запрос;
        Запрос.Текст = "ВЫБРАТЬ
        | РеализацияТоваровУслугТовары.Номенклатура КАК Номенклатура
        |ИЗ
        | Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
        |ГДЕ
        | РеализацияТоваровУслугТовары.Номенклатура = &Номенклатура
        | И РеализацияТоваровУслугТовары.Количество = &Количество
        | И РеализацияТоваровУслугТовары.Автомобиль = &Автомобиль
        | И РеализацияТоваровУслугТовары.Ссылка.Дата >= &Дата";
        Запрос.УстановитьПараметр("Автомобиль", ВыборкаДетальныеЗаписи.Автомобиль);
        Запрос.УстановитьПараметр("Дата", ВыборкаДетальныеЗаписи.Дата);
        Запрос.УстановитьПараметр("Количество", ВыборкаДетальныеЗаписи.Количество);
        Запрос.УстановитьПараметр("Номенклатура", ВыборкаДетальныеЗаписи.Номенклатура);
        Результат = Запрос.Выполнить.Выбрать;
        Пока Результат.Следующий Цикл
            Alfa = 1;
        КонецЦикла;
        
        // Третий вложенный запрос в цикле
        Запрос = Новый Запрос;
        Запрос.Текст = "ВЫБРАТЬ
        | ПартииТоваровНаСкладахОстатки.Номенклатура КАК Номенклатура
        |ИЗ
        | РегистрНакопления.ПартииТоваровНаСкладах.Остатки КАК ПартииТоваровНаСкладахОстатки
        |ГДЕ
        | ПартииТоваровНаСкладахОстатки.Номенклатура = &Номенклатура
        | И ПартииТоваровНаСкладахОстатки.ДокументОприходования.Дата  0"; // Ошибка в условии
        Запрос.УстановитьПараметр("Дата", ВыборкаДетальныеЗаписи.Дата);
        Запрос.УстановитьПараметр("Номенклатура", ВыборкаДетальныеЗаписи.Номенклатура);
        РезультатЗапроса = Запрос.Выполнить.Выбрать;
        Пока РезультатЗапроса.Следующий Цикл
            Для Каждого Элемент Из Слова Цикл
                Если Элемент = "" + ВыборкаДетальныеЗаписи.ОтветственныйПоставщикНаименование + "-" + ВыборкаДетальныеЗаписи.Номенклатура + " " + ВыборкаДетальныеЗаписи.НоменклатураАртикул+" " + "Anbarda olan mal alınıb" тогда
                    Beta = 1;
                КонецЕсли;
            КонецЦикла;
            Если Beta = 0 и Alfa = 0 тогда
                Слова.Добавить("" + ВыборкаДетальныеЗаписи.ОтветственныйПоставщикНаименование + "-" + ВыборкаДетальныеЗаписи.Номенклатура + " " + ВыборкаДетальныеЗаписи.НоменклатураАртикул+" " + "Anbarda olan mal alınıb");
            КонецЕсли;
        КонецЦикла;
        
        // Также есть повтор запроса к РеализацияТоваровУслуг.Товары
        // ...
        Если Alfa = 0 тогда
            Слова.Добавить(ВыборкаДетальныеЗаписи.ОтветственныйПоставщикНаименование + "-" + ВыборкаДетальныеЗаписи.Номенклатура + " " + ВыборкаДетальныеЗаписи.НоменклатураАртикул+" " + ВыборкаДетальныеЗаписи.Автомобиль + " üçün gəlib və Anbarda qalıb(Avtomobil üçün satışa getməyib)");
        КонецЕсли;
    КонецЦикла;
    
    Для Каждого Элемент Из Слова Цикл
        Сообщить(Элемент);
    КонецЦикла;
КонецПроцедуры

Мы видим, что основной запрос получает список товаров из поступлений, а затем для каждой строки этой выборки выполняются дополнительные запросы к документам возврата, реализации и регистру остатков. Такой подход, когда запросы выполняются в цикле, известен как проблема "N+1 запросов" и является крайне неэффективным. Если первый запрос возвращает, например, 1000 строк, то в худшем случае будет выполнен 1 основной запрос + 1000 * 3 (или 4) дополнительных запросов, что в сумме может достигать тысяч обращений к базе данных. Именно это, скорее всего, и является основной причиной задержки в 40 секунд.

Решение 1: Оптимизация запросов

Прежде чем переходить к фоновым заданиям, крайне важно максимально оптимизировать саму процедуру. Часто правильно составленный запрос может сократить время выполнения с десятков секунд до долей секунды.

Мы рекомендуем рассмотреть следующие шаги:

  1. Объединение запросов: Постарайтесь переписать все вложенные запросы таким образом, чтобы они стали частью одного большого запроса. Для этого можно использовать операторы СОЕДИНЕНИЕ (ЛЕВОЕ СОЕДИНЕНИЕ, ВНУТРЕННЕЕ СОЕДИНЕНИЕ и т.д.) или ВЫБРАТЬ РАЗРЕШЕННЫЕ для работы с виртуальными таблицами остатков.
  2. Использование временных таблиц: Если логика слишком сложна для одного запроса, разбейте её на несколько этапов с использованием временных таблиц. Результаты одного запроса помещайте во временную таблицу, а затем используйте её в последующих запросах. Это все равно будет намного быстрее, чем выполнять запросы в цикле по строкам выборки.
  3. Оптимизация условий: Убедитесь, что все условия в запросах используют индексы базы данных. Особенно это касается полей, по которым идет фильтрация (Дата, ссылки на объекты, коды, наименования).
  4. Работа с агрегатными функциями: Если вам нужны только итоговые данные или проверки на существование (как Alfa = 0 или Beta = 0), используйте агрегатные функции (СУММА, КОЛИЧЕСТВО, МИНИМУМ, МАКСИМУМ) и группировки в запросе.

Пример общей структуры оптимизированного запроса (концептуально):


Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    ПоступлениеТоваровУслугТовары.Номенклатура,
|    ПоступлениеТоваровУслугТовары.Ссылка.Дата КАК ДатаПоступления,
|    ПоступлениеТоваровУслугТовары.Номенклатура.Артикул КАК НоменклатураАртикул,
|    ПоступлениеТоваровУслугТовары.Автомобиль КАК Автомобиль,
|    ПоступлениеТоваровУслугТовары.Ссылка.ОтветственныйПоставщик.Наименование КАК ОтветственныйПоставщикНаименование,
|    ПоступлениеТоваровУслугТовары.Количество КАК КоличествоПоступления,
|    ЕСТЬNULL(Возвраты.КоличествоВозвратов, 0) КАК КоличествоВозвратов,
|    ЕСТЬNULL(Реализации.КоличествоРеализаций, 0) КАК КоличествоРеализаций,
|    ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК КоличествоОстатокНаДатуПоступления
|ИЗ
|    Документ.ПоступлениеТоваровУслуг.Товары КАК ПоступлениеТоваровУслугТовары
|
|    // Левое соединение с данными по возвратам
|    ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
|        ВозвратТоваровПоставщикуТовары.Номенклатура КАК Номенклатура,
|        ВозвратТоваровПоставщикуТовары.Количество КАК КоличествоВозвратов,
|        ВозвратТоваровПоставщикуТовары.Ссылка.Дата КАК ДатаВозврата
|    ИЗ
|        Документ.ВозвратТоваровПоставщику.Товары КАК ВозвратТоваровПоставщикуТовары
|    ГДЕ
|        ВозвратТоваровПоставщикуТовары.Ссылка.Дата > &ДатаНачалаПериода
|    ) КАК Возвраты
|    ПО ПоступлениеТоваровУслугТовары.Номенклатура = Возвраты.Номенклатура
|    И ПоступлениеТоваровУслугТовары.Количество = Возвраты.КоличествоВозвратов
|    И ПоступлениеТоваровУслугТовары.Ссылка.Дата < Возвраты.ДатаВозврата
|
|    // Левое соединение с данными по реализациям
|    ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
|        РеализацияТоваровУслугТовары.Номенклатура КАК Номенклатура,
|        РеализацияТоваровУслугТовары.Количество КАК КоличествоРеализаций,
|        РеализацияТоваровУслугТовары.Автомобиль КАК Автомобиль,
|        РеализацияТоваровУслугТовары.Ссылка.Дата КАК ДатаРеализации
|    ИЗ
    |        Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
|    ГДЕ
|        РеализацияТоваровУслугТовары.Ссылка.Дата >= &ДатаНачалаПериода
|    ) КАК Реализации
|    ПО ПоступлениеТоваровУслугТовары.Номенклатура = Реализации.Номенклатура
|    И ПоступлениеТоваровУслугТовары.Количество = Реализации.КоличествоРеализаций
|    И ПоступлениеТоваровУслугТовары.Автомобиль = Реализации.Автомобиль
|    И ПоступлениеТоваровУслугТовары.Ссылка.Дата <= Реализации.ДатаРеализации
|
|    // Левое соединение с остатками на складах на дату поступления (виртуальная таблица Остатки)
|    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ПартииТоваровНаСкладах.Остатки(&Дата2,
|        Номенклатура В
|            (ВЫБРАТЬ РАЗЛИЧНЫЕ ПоступлениеТоваровУслугТовары.Номенклатура
|            ИЗ Документ.ПоступлениеТоваровУслуг.Товары КАК ПоступлениеТоваровУслугТовары
|            ГДЕ ПоступлениеТоваровУслугТовары.Ссылка.Дата МЕЖДУ &Дата И &Дата2
|            И ПоступлениеТоваровУслугТовары.Ссылка.ОтветственныйПоставщик.Наименование <> ""Rəhbərlik""
|            И ПоступлениеТоваровУслугТовары.Автомобиль.Наименование <> ""Anbar"")) КАК Остатки
|    ПО ПоступлениеТоваровУслугТовары.Номенклатура = Остатки.Номенклатура
|
|ГДЕ
|    ПоступлениеТоваровУслугТовары.Ссылка.Дата МЕЖДУ &Дата И &Дата2
|    И ПоступлениеТоваровУслугТовары.Ссылка.ОтветственныйПоставщик.Наименование <> ""Rəhbərlik""
|    И ПоступлениеТоваровУслугТовары.Автомобиль.Наименование <> ""Anbar""
|
|УПОРЯДОЧИТЬ ПО
|    ПоступлениеТоваровУслугТовары.Номенклатура, ПоступлениеТоваровУслугТовары.Ссылка.Дата
|";

Запрос.УстановитьПараметр("Дата", Дата('2023.12.01 00:00:00'));
Запрос.УстановитьПараметр("Дата2", ТекущаяДата() - 60*60*24*2);
Запрос.УстановитьПараметр("ДатаНачалаПериода", Дата('2023.12.01 00:00:00')); // Пример, может потребоваться корректировка

РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать(); 

Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
    // Теперь вся необходимая информация доступна в одной строке выборки
    // и не нужно выполнять запросы в цикле.
    // Здесь мы можем анализировать полученные поля:
    // ВыборкаДетальныеЗаписи.КоличествоВозвратов (если есть возврат)
    // ВыборкаДетальныеЗаписи.КоличествоРеализаций (если есть реализация)
    // ВыборкаДетальныеЗаписи.КоличествоОстатокНаДатуПоступления (остаток на складе)
    // и формировать сообщения на основе этих данных.

    Alfa = 0;
    Beta = 0;

    Если ВыборкаДетальныеЗаписи.КоличествоВозвратов > 0 ИЛИ ВыборкаДетальныеЗаписи.КоличествоРеализаций > 0 Тогда
        Alfa = 1;
    КонецЕсли;

    // Проверка наличия уже добавленных сообщений в Слова
    СтрокаСообщенияAnbar = "" + ВыборкаДетальныеЗаписи.ОтветственныйПоставщикНаименование + "-" + ВыборкаДетальныеЗаписи.Номенклатура + " " + ВыборкаДетальныеЗаписи.НоменклатураАртикул+" " + "Anbarda olan mal alınıb";
    Для Каждого Элемент Из Слова Цикл
        Если Элемент = СтрокаСообщенияAnbar Тогда
            Beta = 1;
            Прервать;
        КонецЕсли;
    КонецЦикла;

    Если Beta = 0 И Alfa = 0 Тогда
        Слова.Добавить(СтрокаСообщенияAnbar);
    КонецЕсли;

    Если Alfa = 0 Тогда
        Слова.Добавить(ВыборкаДетальныеЗаписи.ОтветственныйПоставщикНаименование + "-" + ВыборкаДетальныеЗаписи.Номенклатура + " " + ВыборкаДетальныеЗаписи.НоменклатураАртикул+" " + ВыборкаДетальныеЗаписи.Автомобиль + " üçün gəlib və Anbarda qalıb(Avtomobil üçün satışa getməyib)");
    КонецЕсли;

КонецЦикла;

// В данном случае, сообщения не выводятся тут, а возвращаются для клиента.
// Для Каждого Элемент Из Слова Цикл
//     Сообщить(Элемент);
// КонецЦикла;
// Процедура должна вернуть Слова или адрес хранилища
// Возврат Слова;
КонецПроцедуры

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

Решение 2: Использование фоновых заданий для асинхронного выполнения

Механизм фоновых заданий в 1С:Предприятии позволяет выполнять ресурсоемкие операции на сервере в отдельном потоке, не блокируя пользовательский интерфейс. Это идеальное решение, когда нам нужно выполнить длительную задачу, а пользователь при этом должен продолжать работать.

  1. Подготовка серверной процедуры для фонового задания:

    Мы должны модифицировать нашу процедуру Yoxla (или её оптимизированную версию) таким образом, чтобы она была Экспортной и работала на сервере. Самое главное: вместо того чтобы выводить сообщения напрямую (Сообщить), процедура должна возвращать массив сообщений как результат своей работы.

    В общем модуле, который будет вызван фоновым заданием, создадим экспортную процедуру. Добавим к ней комментарий // &НаСервере, чтобы явно указать её серверное назначение, если модуль имеет клиент-серверный контекст.

    Пример модифицированной процедуры в общем модуле (сервер):

    
    // В общем модуле, например, "TələbnaməYoxlanışı"
    &НаСервере
    Процедура YoxlaФоновоеЗадание(ПараметрыФоновогоЗадания) Экспорт
    
        // Инициализируем массив для сбора сообщений
        Слова = Новый Массив;
        Слова.Добавить("Bildirişlər"); // Начальное сообщение
    
        // Здесь размещаем всю вашу оптимизированную логику запросов.
        // Вместо прямого вызова Сообщить(Элемент) заполняем массив Слова.
    
        // Примерная логика формирования сообщений (используя данные из оптимизированного запроса)
        // Пусть ПараметрыФоновогоЗадания содержит необходимые параметры для запроса,
        // например, НачальнаяДата, КонечнаяДата и т.д.
        
        Запрос = Новый Запрос;
        Запрос.Текст = "
        // ... Ваш оптимизированный запрос, аналогичный примеру выше ...
        ";
        
        // Устанавливаем параметры запроса из ПараметрыФоновогоЗадания
        // Запрос.УстановитьПараметр("Дата", ПараметрыФоновогоЗадания.НачальнаяДата);
        // Запрос.УстановитьПараметр("Дата2", ПараметрыФоновогоЗадания.КонечнаяДата);
        // ...
        
        РезультатЗапроса = Запрос.Выполнить();
        ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать(); 
    
        Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
            // Логика формирования сообщений на основе выборки
            Alfa = 0;
            Beta = 0;
    
            Если ВыборкаДетальныеЗаписи.КоличествоВозвратов > 0 ИЛИ ВыборкаДетальныеЗаписи.КоличествоРеализаций > 0 Тогда
                Alfa = 1;
            КонецЕсли;
            
            // Проверка наличия уже добавленных сообщений в Слова (если это нужно)
            СтрокаСообщенияAnbar = "" + ВыборкаДетальныеЗаписи.ОтветственныйПоставщикНаименование + "-" + ВыборкаДетальныеЗаписи.Номенклатура + " " + ВыборкаДетальныеЗаписи.НоменклатураАртикул+" " + "Anbarda olan mal alınıb";
            Для Каждого Элемент Из Слова Цикл
                Если Элемент = СтрокаСообщенияAnbar Тогда
                    Beta = 1;
                    Прервать; // Выходим из внутреннего цикла, если элемент найден
                КонецЕсли;
            КонецЦикла;
    
            Если Beta = 0 И Alfa = 0 Тогда
                Слова.Добавить(СтрокаСообщенияAnbar);
            КонецЕсли;
    
            Если Alfa = 0 Тогда
                Слова.Добавить(ВыборкаДетальныеЗаписи.ОтветственныйПоставщикНаименование + "-" + ВыборкаДетальныеЗаписи.Номенклатура + " " + ВыборкаДетальныеЗаписи.НоменклатураАртикул+" " + ВыборкаДетальныеЗаписи.Автомобиль + " üçün gəlib və Anbarda qalıb(Avtomobil üçün satışa getməyib)");
            КонецЕсли;
        КонецЦикла;
        
        // После завершения формирования массива, помещаем его во временное хранилище
        // и возвращаем его адрес. Это позволит передать большой объем данных на клиент.
        АдресХранилища = ПоместитьВоВременноеХранилище(Слова, ПараметрыФоновогоЗадания.ИдентификаторПользователя);
        Возврат АдресХранилища; // Возвращаем адрес хранилища как результат фонового задания
    КонецПроцедуры
    
  2. Запуск фонового задания с клиента:

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

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

    
    &НаКлиенте
    Перем ИдентификаторФоновогоЗадания; // Объявляем переменную для хранения идентификатора задания
    
    &НаКлиенте
    Процедура ПриОткрытии(Отказ)
        // Мы можем показать сообщение, что данные загружаются, чтобы уведомить пользователя
        ПоказатьОповещениеПользователя("Загрузка данных", , "Проверка поступлений выполняется в фоновом режиме...", "Загрузка");
    
        // Формируем параметры, которые будут переданы в фоновое задание.
        // Важно, чтобы параметры были сериализуемыми.
        ПараметрыЗадания = Новый Структура;
        ПараметрыЗадания.Вставить("ИдентификаторПользователя", Пользователи.ТекущийПользователь().УникальныйИдентификатор);
        // Добавляем другие необходимые параметры, например, даты, фильтры и т.д.
        // ПараметрыЗадания.Вставить("НачальнаяДата", Дата('2023.12.01 00:00:00'));
        // ПараметрыЗадания.Вставить("КонечнаяДата", ТекущаяДата() - 60*60*24*2);
    
        // Запускаем фоновое задание.
        // "TələbnaməYoxlanışı.YoxlaФоновоеЗадание" - это полное имя процедуры в общем модуле.
        ИдентификаторФоновогоЗадания = ВыполнитьАсинхронно("TələbnaməYoxlanışı.YoxlaФоновоеЗадание", ПараметрыЗадания);
    
        // Подключаем обработчик ожидания для периодической проверки состояния фонового задания.
        // "ПроверитьФоновоеЗадание" будет вызываться каждую 1 секунду, пока его не отключат.
        ПодключитьОбработчикОжидания("ПроверитьФоновоеЗадание", 1, Истина);
    КонецПроцедуры
    
  3. Отслеживание выполнения и получение результата на клиенте:

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

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

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

    
    &НаКлиенте
    Процедура ПроверитьФоновоеЗадание()
        Если ИдентификаторФоновогоЗадания <> Неопределено Тогда
            // Получаем текущее состояние фонового задания
            СостояниеЗадания = ФоновыеЗадания.Состояние(ИдентификаторФоновогоЗадания);
    
            Если СостояниеЗадания = СостояниеФоновогоЗадания.Выполнено Тогда
                // Если задание успешно выполнено
                ОтключитьОбработчикОжидания("ПроверитьФоновоеЗадание"); // Отключаем обработчик ожидания
    
                // Получаем объект фонового задания по идентификатору
                Задание = ФоновыеЗадания.Получить(ИдентификаторФоновогоЗадания);
    
                Если Задание.Результат <> Неопределено Тогда
                    // Задание.Результат содержит адрес временного хранилища
                    АдресХранилища = Задание.Результат;
                    // Получаем массив сообщений из временного хранилища
                    МассивСообщений = ПолучитьИзВременногоХранилища(АдресХранилища);
    
                    Если МассивСообщений <> Неопределено Тогда
                        // Выводим каждое сообщение пользователю
                        Для Каждого Элемент Из МассивСообщений Цикл
                            Сообщить(Элемент);
                        КонецЦикла;
                    КонецЕсли;
                КонецЕсли;
    
                ИдентификаторФоновогоЗадания = Неопределено; // Сбрасываем идентификатор задания
    
            ИначеЕсли СостояниеЗадания = СостояниеФоновогоЗадания.Ошибка Тогда
                // Если в процессе выполнения фонового задания произошла ошибка
                ОтключитьОбработчикОжидания("ПроверитьФоновоеЗадание"); // Отключаем обработчик ожидания
    
                // Получаем объект фонового задания, чтобы узнать подробности ошибки
                Задание = ФоновыеЗадания.Получить(ИдентификаторФоновогоЗадания);
                Сообщить("Ошибка выполнения фонового задания: " + Задание.Ошибка.Описание, СтатусСообщения.Важное);
    
                ИдентификаторФоновогоЗадания = Неопределено; // Сбрасываем идентификатор задания
    
            КонецЕсли;
        КонецЕсли;
    КонецПроцедуры
    
  4. Особенности использования ВременногоХранилища:

    Когда фоновое задание возвращает результат, этот результат передается по сети. Если объем данных большой (как в случае с массивом строк), прямая передача через свойство Результат может быть неоптимальной или даже вызвать ошибки. Использование ПоместитьВоВременноеХранилище() на сервере (в фоновом задании) позволяет сохранить данные на сервере, а на клиент вернуть только адрес этого хранилища. На клиенте вы затем используете ПолучитьИзВременногоХранилища() по этому адресу, чтобы забрать данные. Это более эффективный способ передачи больших объемов информации между сервером и клиентом. Важно помнить, что данные во временном хранилище существуют ограниченное время, но для обработки результатов фонового задания этого времени обычно достаточно.

Сочетание оптимизации и фоновых заданий

Наилучший подход для вашей задачи — это комбинация оптимизации запросов и использования фоновых заданий.

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

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

← На главную