Книга знаний

1С:Предприятие / Приемы программирования / Встроенный язык

Асинхронные вычисления без «заморозки» формы (на фоновых заданиях)

В бытность свою студентом, курсе на 1, открыл для себя в Delphi замечательную возможность создания
<br>многопоточных приложений. По делу и без дела, делал
<br><br>многопоточными все свои поделки – было интересно, хоть тогда у меня еще не было многоядерных
<br>процессоров. Постепенно мозги привыкли к этому, и когда я
<br><br>занялся 1С 8, меня ожидало разочарование – запущенный из формы код замораживал не только
саму
<br>форму, но и окно приложения заодно. Грустно. «Финты ушами» с
<br><br>использованием генератора «Внешних событий» по душе не пришлись. А до фоновых заданий в
версии
<br>1С 8.1 добрался только сейчас.
Автор статьи: shachneff | Редакторы: oleg_km, dj_serega
Последняя редакция №15 от 23.06.14 | История
URL: http://kb.mista.ru/article.php?id=696

Ключевые слова: 1cv81, фоновое задание, асинхронно, многопоточность


Дано:

1С:Предприятие > 8.1.8, клиент-серверная архитектура, MS SQL.
Общий неглобальный серверный модуль. Обработка с одной формой, одной кнопкой и одним табличным полем.

Цель:

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

Методика:

Как показала практика, из отработавшего фонового задания вернуть штатным путем каке-либо данные невозможно. Имеется 2 способа, позволяющих это
делать:
1) фоновое задание пишет результат своей работы в БД, обработка отслеживает, что фоновое задание завершилось и считывает из БД результат. В качестве места хранения результата предлагается
использовать регистр сведений, где измерение хранит UID экземпляра фонового задания, а ресурс имеет тип ХранилищеЗначений.
2) извращенный способ – передать значение через программную генерацию исключения  (оператор «ВызватьИсключение»).

Решение:


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

Процедура общего модуля:

Процедура ФоноваяПроцедура(Вход) Экспорт
    
   ВремяСтарта = ТекущаяДата();
   Длит = 15; //15 секунд
   Пока (ТекущаяДата() - ВремяСтарта) < Длит Цикл
   //холостой цикл-замедлитель        
   КонецЦикла;

   //чтобы не передавать примитивные типы,
   //создадим ТЗ из двух колонок, в одну из которых положим значение входного параметра,
   // а во вторую - выходного
   ТЗ = Новый ТаблицаЗначений;
   ТЗ.Колонки.Добавить("Вход");
   ТЗ.Колонки.Добавить("Выход");
   
   НС = ТЗ.Добавить();
   НС.Вход = Вход;
   НС.Выход = ТекущаяДата() + 3600; 
   
   //упакуем объект ТаблицаЗначений и сериализуем его в строку. Для больших таблиц - хороший выигрыш в объеме.
   Упаковано = ЗначениеВСтрокуВнутр(Новый ХранилищеЗначения(ТЗ, Новый СжатиеДанных(9)));
   
   
   ВызватьИсключение Упаковано; //самое главное, обманем систему, сказав, что возникла ошибка, и в качестве описания ошибки подсунем возвращаемое значение
    
КонецПроцедуры



Модуль формы обработки:

Перем Ключ;

Процедура КнопкаВыполнитьНажатие(Кнопка)
    
    //размерность массива - по числу параметров запускаемой в фоновом режиме процедуры
    МассивПараметров = Новый Массив(1);
    МассивПараметров[0] = "123-входной параметр-строка";
    Ключ = Новый УникальныйИдентификатор; //чтобы отличить наше фоновое задание от других
    
    //внимание! Первый параметр - полный путь к запускаемой процедуре, включая имя общего модуля, в котором она находится.
    ФоновыеЗадания.Выполнить("МодульРегламентныхЗаданий.ФоноваяПроцедура", МассивПараметров, Ключ, "Тестовое ФЗ"); // запуск
        
    ПодключитьОбработчикОжидания("ЗаполнитьТП", 1); //ждем, пока ФЗ нам что-то вернет.
    //Важно! В данном случае применить метод ОжидатьЗавершения не получится. Если получится - пишите как.
    
КонецПроцедуры

Процедура ЗаполнитьТП() //просто заполняет ТабличноеПоле на форме, демонстрируя, что все работает.
    
    ФильтрОтбора = Новый Структура("Ключ,Наименование,Состояние", Ключ, "Тестовое ФЗ", СостояниеФоновогоЗадания.ЗавершеноАварийно);
    МассивЗаданий = ФоновыеЗадания.ПолучитьФоновыеЗадания(ФильтрОтбора);
    
    Если МассивЗаданий.Количество() > 0 И НЕ МассивЗаданий[0].ИнформацияОбОшибке.Описание = "" Тогда
        //получили данные из Фонового задания, поместили их в Табличное поле
        ТП = ЗначениеИзСтрокиВнутр(МассивЗаданий[0].ИнформацияОбОшибке.Описание).Получить();
        ЭлементыФормы.ТП.СоздатьКолонки(); //отобразили то, что поместили.
        ОтключитьОбработчикОжидания("ЗаполнитьТП"); //прекратили ожидать результат от фонового задания.
    КонецЕсли;    
    
КонецПроцедуры


Результат:



Жмем кнопку. 15 секунд наслаждаемся перетаскиванием формы и прочими действиями (кроме закрытия).
Потом резко (само! шайтан! :) заполнится табличное поле на форме, чего и следовало ожидать.
Нагрузочные испытания проведены на передаче таблицы значений из 2 полей с количеством строк 100 тыс.
шт. – успешно.


Надеюсь, статья будет вам полезна.
Выражаю благодарность всем, кто помог мне осуществить данную операцию.

С уважением, Алексей Шачнев.


Конец.

Вместо обработки ожидания использовал отправку датаграммы из фонового задания форме посредством MS WinSock (ActiveX)

oleg_km



Нужно было выполнить заполнение дерева в фоне (что бы не блокировать основной интерфейс). Нашел эту статейку.
Собственно удалось выполнить фоновое задание без "ВызватьИсключение".
8.2.19.68 (Управляемое приложение)

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


&НаКлиенте
Процедура ВыполнитьВФоне(Команда)
    
    // Работа с фоновыми заданиями возможна только на сервере
    ВыполнитьВФонеНаСервере();
    
    // Подключать обработчики ожидания можно на клиенте
    ПодключитьОбработчикОжидания("ОжиданиеРезультатаВыполнения", 1);
    
КонецПроцедуры

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

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

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

&НаСервере
Процедура ОтменитьЗаданиеНаСервере()  
    
    // Выполним поиск фонового задания по Уникальному идентификатору формы, Наименованию и состоянию.
    СтруктураОтбора = Новый Структура("Ключ, Наименование, Состояние", УникальныйИдентификатор, "Вывод данных через фоновое задание", СостояниеФоновогоЗадания.Активно);
    
    МассивЗаданий = ФоновыеЗадания.ПолучитьФоновыеЗадания(СтруктураОтбора);
    
    Если МассивЗаданий.Количество() Тогда
        // Задание найдено. Можно его отменить.
        
        МассивЗаданий[0].Отменить();
        
    КонецЕсли;
    
КонецПроцедуры // ПроверкаВыполненияНаСервере

Код с общего модуля:
Процедура ВыполнитьФоновыйКод() Экспорт 
    
    СтруктураПараметров = XMLЗначение(Тип("ХранилищеЗначения"), Параметр).Получить();
    
    Дерево = СтруктураПараметров.Дерево;
    
    // Выполняем произвольный код
    ...
    ...
    
    
    // Подготавливаем данные в обработку
    
    ТЗXML = XMLСтрока(Новый ХранилищеЗначения(Дерево));
    
    ХранилищеОбщихНастроек.Сохранить("ВыполнениеФоновогоЗадания", "ВыполнениеФоновогоЗадания", ТЗXML);
    
КонецПроцедуры // ВыполнитьФоновыйКод

с Уважением, dj_serega.

Описание | Рубрикатор | Поиск | ТелепатБот | Захваченные статьи | Установки | Форум
© Станислав Митичкин (Волшебник), 2005-2024 | Mista.ru

Яндекс.Метрика