Для тех, кто не хочет читать статью полностью и разбираться в деталях, можно сразу перейти в конец и скопировать себе целиком код модуля формы и обработки - они рабочие. Только перед этим удостоверьтесь в том, что у вас такая же версия БСП, как и у автора.
У многих программистов возникает вопрос, как средствами БСП выполнить код внешней обработки асинхронно и можно ли это сделать в принципе, не подключая обработку к конфигурации через механизм дополнительных отчетов и обработок. Сегодня я разберу этот вопрос с анализом приводимого кода. Поехали.
Данная статья актуальна для БСП версии 3.1.10.369 и давайте сразу узнаем, где можно посмотреть эту самую версию.
Как узнать версию БСП для вашей конфигурации 1С?
Вариант 1. Посмотреть в коде конфигурации.
Для этого вам понадобится общий модуль ОбновлениеИнформационнойБазыБСП процедура ПриДобавленииПодсистемы(...). В коде этой процедуры можно увидеть текущую версию библиотеки. Для моего случая это выглядит так:
// Заполняет основные сведения о библиотеке или основной конфигурации. Процедура ПриДобавленииПодсистемы(Описание) Экспорт Описание.Имя = "СтандартныеПодсистемы"; Описание.Версия = "3.1.10.369"; Описание.ИдентификаторИнтернетПоддержки = "SSL"; Описание.РежимВыполненияОтложенныхОбработчиков = "Параллельно"; Описание.ПараллельноеОтложенноеОбновлениеСВерсии = "2.3.3.0"; Описание.ЗаполнятьДанныеНовыхПодсистемПриПереходеСДругойПрограммы = Истина; КонецПроцедуры
Вариант 2. Посмотреть в регистре сведений "ВерсииПодсистем".
Это делается под пользователем с полными правами через меню "Функции для технического специалиста" из режима предприятия.
В регистре нужно найти строку "СтандартныеПодсистемы" и посмотреть версию в соответствующей колонке.
Итак, с версией разобрались. Теперь создадим внешнюю обработку и создадим в ней команду ВыполнитьФункциюАсинхронно. Вытащим ее на форму в виде кнопки и привяжем к ней процедуру-обработчик ВыполнитьФункциюАсинхронно(Команда).
Теперь у нас есть 2 проблемы, которые надо решить:
- Фоновое задание может выполняться только на сервере, а мы пока на клиенте.
- По условиям задачи код фонового задания должен располагаться в модуле нашей обработки, а значит процедура, которая будет его запускать должна получить контекст модуля. Но это пока просто файл, а подключать мы его не хотим.
Чтобы решить эти 2 задачи самостоятельно придется основательно покопаться в общем модуле ДлительныеОперации, входящем в состав БСП. У начинающего специалиста на это может уйти ни один день, поэтому я немного сэкономлю вам время и сразу подскажу, что нас будет интересовать процедура ВыполнитьФункцию(...)
Среди объемного описания этой функции можно найти параметр "ИмяФункции", в комментарии к которому сказано, что в него можно передать имя экспортной функции модуля обработки и даже приведен пример "Обработка.ЗагрузкаДанных.МодульОбъекта.Загрузить". Это уже хорошо, но как передать контекст этого модуля все равно непонятно.
Давайте смотреть описание первого параметра "ПараметрыВыполнения". В нем сказано, что оно может являться структурой, а состав этой структуры нужно смотреть в описании к методу "ПараметрыВыполненияФункции". Отлично, - давайте посмотрим, что там...
Читаем описание и узнаем, что процедура является конструктором коллекции ПараметрыВыполненияФункции для функции ВыполнитьФункцию, а в составе полей этой коллекции есть ВнешнийОтчетОбработка, тип которого двоичные данные. Кажется, это то, что нам и нужно!
Промежуточный план такой:
- Перевести внешнюю обработку в формат двоичных данных;
- Вызвать функцию ДлительныеОперации.ПараметрыВыполненияФункции(...) для формирования шаблона-коллекции;
- Передать полученную коллекцию вместе с двоичными данными обработки из п.1, как параметр, в функцию ДлительныеОперации.ВыполнитьФункцию(...)
Реализуем этот план через функцию НачатьВыполнениеДлительнойОперации(...)
Функция НачатьВыполнениеДлительнойОперации(ДвоичныеДанныеОбработки) ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияФункции(УникальныйИдентификатор); ПараметрыВыполнения.ВнешнийОтчетОбработка = ДвоичныеДанныеОбработки; ДлительнаяОперация = ДлительныеОперации.ВыполнитьФункцию(ПараметрыВыполнения, "ВнешняяОбработка."+ПолучитьИмяОбработки()+".МодульОбъекта.МояФункция"); // Если после второй точки не написано "МодульОбъекта" тогда БСП будет пытаться вызвать // экспортную функцию общего модуля или модуля менеджера объекта Возврат ДлительнаяОперация; КонецФункции
На вход передаем двоичные данные обработки, которые нам еще предстоит получить. Из метода ДлительныеОперации.ПараметрыВыполненияФункции(...) получаем структуру "ПараметрыВыполнения". В нее записываем двоичные данные нашей обработки и передаем далее в метод ДлительныеОперации.ВыполнитьФункцию(...) первым параметром.Отдельно стоит обратить внимание на второй параметр метода, куда мы передаем путь к функции, которая будет выполняться фоном. До первой точки у нас идет ключевое слово "ВнешняяОбработка".
Именно, ориентируясь на него, БСП будет понимать, что контекстом выполнения нашей функции будет являться модуль внешней обработки, который надо взять из параметра структуры.
После второй точки идет название обработки. Я получаю его вспомогательной функцией "ПолучитьИмяОбработки()". Затем ключевое слово МодульОбъекта - если его не задать, как сначала делал я, то ничего не выйдет, т.к. БСП будет думать, что мы вызываем функции общих модулей или модулей менеджера.
После третьей точки идет название функции модуля нашей обработки.
Теперь нам надо получить двоичные данные. Сделаем это красиво, по максимуму задействовав БСП. Для этого нам нужно отправить файл внешней обработки с клиента, где мы нажали кнопку "Выполнить функцию асинхронно" на сервер. Я использую для этого функцию модуля ФайловаяСистемаКлиент и процедуру ЗагрузитьФайл(...) - результатом работы которой будет, как раз, адрес хранения двоичных данных обработки на сервере.
Процедура ОтправитьФайлНаСервер(ИмяФайла, ОбработчикОповещения) ПараметрыЗагрузки = ФайловаяСистемаКлиент.ПараметрыЗагрузкиФайла(); ПараметрыЗагрузки.ИдентификаторФормы = УникальныйИдентификатор; ПараметрыЗагрузки.Интерактивно = Ложь; ФайловаяСистемаКлиент.ЗагрузитьФайл(ОбработчикОповещения, ПараметрыЗагрузки, ИмяФайла); КонецПроцедуры
В этой функции мы получаем параметры загрузки, которые так же как и в случае с вызовом длительной операции, выступают "эталонной" структурой для передачи в функцию отправки файла на сервер.
В структуру параметров нам важно передать идентификатор формы обработки, из которой мы осуществляем вызов, а также запретить вывод окна выбора файла, т.к. он и так нам известен. За это отвечает параметр "Интерактивно", который мы должны установить в "Ложь".
Теперь напишем функцию, принимающую файл на сервере:
Процедура ПослеПомещенияФайлаНаСервер(Результат, ДополнительныеПараметры) Экспорт Если Не ЗначениеЗаполнено(Результат.Имя) Тогда Возврат; КонецЕсли; ДвоичныеДанныеОбработки = ПолучитьИзВременногоХранилища(Результат.Хранение); ДлительнаяОперация = НачатьВыполнениеДлительнойОперации(ДвоичныеДанныеОбработки); // Во время выполнения длительной операции будет открыто окно с указанным заголовоком и текстом ПараметрыОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект); ПараметрыОжидания.Заголовок = "Пожалуйста, подождите"; ПараметрыОжидания.ТекстСообщения = "Идет выполнение длительной операции"; ПараметрыОжидания.ВыводитьСообщения = Истина; // если оставить "Ложь" (по умолч.), то // накопленные сообщения не будут выведены пользователю при возврате управления из // фоновой процедуры (функции) на клиент // После завершения длительной операции будет вызвано оповещение с текстом и пояснением ОповещениеПользователя = ПараметрыОжидания.ОповещениеПользователя; ОповещениеПользователя.Показать = Истина; ОповещениеПользователя.Текст = "Обработка завершена"; ОповещениеПользователя.Пояснение = "Выполнение длительной операции завершено"; ОписаниеОповещения = Новый ОписаниеОповещения("ЗавершениеДлительнойОперации", ЭтотОбъект); ДлительныеОперацииКлиент.ОжидатьЗавершение(ДлительнаяОперация, ОписаниеОповещения, ПараметрыОжидания); КонецПроцедуры
В начале идет проверка на заполненность свойства "Имя". Если оно пустое, значит на сервер мы ничего не передали и, соответственно, сделать ничего не можем.
Затем получаем двоичные данные обработки. Они, в результате работы функции ОтправитьФайлНаСервер(...), сохраняются во временном хранилище, откуда мы их должны забрать и передать далее в функцию НачатьВыполнениеДлительнойОперации(...), которую написали ранее. При выполнении длительной операции БСП превратит двоичные данные в контекст модуля и сможет выполнить нашу функцию.
После начала выполнения длительной операции мы должны позаботиться о сервисных вещах, чтобы наш пользователь не скучал. Во-первых, когда он будет смотреть на заставку веселого 1С-котика, то должен понимать, какая операция сейчас выполняется. Для этого мы используем свойства структуры "ПараметрыОжидания" - в них мы прописываем заголовок окна с котиком и уточняющий текст.
Ну, а когда котик исчезнет, что будет означать окончание асинхронной функции, то мы покажем ему "всплывашку" с текстом и пояснением.
Важный момент:
Если в вашей процедуре используются сообщения - метод платформы Сообщить(...), то не забудьте свойство "ВыводитьСообщения" установить в "Истина".
Я однажды забыл и долго думал, что за глюк-то такой...
В конце идет описание обработчика, который будет вызван по окончанию длительной операции. В качестве такого обработчика у нас будет выступать экспортная функция:
Процедура ЗавершениеДлительнойОперации(РезультатВыполнения, ДополнительныеПараметры) Экспорт Если РезультатВыполнения = Неопределено Тогда Возврат; КонецЕсли; Если РезультатВыполнения.Статус = "Ошибка" Тогда СтандартныеПодсистемыКлиент.ВывестиИнформациюОбОшибке(РезультатВыполнения.ИнформацияОбОшибке); Возврат; КонецЕсли; Если РезультатВыполнения.Статус = "Выполнено" Тогда РезультатОперации = ПолучитьИзВременногоХранилища(РезультатВыполнения.АдресРезультата); // здесь результат УдалитьИзВременногоХранилища(РезультатВыполнения.АдресРезультата); КонецЕсли; КонецПроцедуры
А теперь вернемся на клиент и наконец напишем вызов всей этой штуковины:
Процедура ВыполнитьФункциюАсинхронно(Команда) ОбработчикОповещения = Новый ОписаниеОповещения("ПослеПомещенияФайлаНаСервер", ЭтотОбъект); ИмяФайла = ПолучитьИмяФайлаОбработки(); ОтправитьФайлНаСервер(ИмяФайла, ОбработчикОповещения); КонецПроцедуры Функция ПолучитьИмяФайлаОбработки() ОбъектОбработки = РеквизитФормыВЗначение("Объект"); Возврат ОбъектОбработки.ИспользуемоеИмяФайла; КонецФункции
Здесь у меня получилось не очень красиво из-за промежуточного серверного вызова, но к сожалению более красивого варианта, который был бы еще и универсальным, пока не нашел. Если у вас будут варианты, как передать имя файла без серверного вызова (но не хардкорно!) - пишите в комментариях, буду рад)
Ну и чтобы наш пример был завершен, давайте напишем какую-нибудь процедуру для модуля обработки, демонстрирующую, что у нас все работает.
Важный нюанс. Чтобы обработка работала стабильно, необходимо написать еще одну служебную процедуру.
Процедура НазначитьКлючУникальности() Если КлючУникальности = Неопределено Тогда ИмяОбработки = СтрРазделить(ЭтотОбъект.ИмяФормы, ".")[1]; КлючУникальности = ИмяОбработки + XMLСтрока(ТекущаяДата()); КонецЕсли; КонецПроцедуры
Вызов этой процедуры надо прописать в обработчике ПриОткрытии(). Это нужно для того, чтобы обработка могла стабильно передавать файлы с клиента на сервер. В противном случае, при многократных попытках исполнения метода, может возникать ошибка.
А теперь давайте для полноты картины напишем какую-нибудь функцию модуля обработки.
Функция МояФункция() Экспорт Для Инд = 1 По 1000000 Цикл // что-то делаем... КонецЦикла; Сообщить("Длительная процедура успешно выполнена"); Возврат 100500; КонецФункции
Отлично! Запустим и посмотрим на результат работы.
Вот так выглядит выполнение длительной операции:
А вот так завершение:
Теперь поймаем результат длительной операции в отладчике:
Ну вот, кажется и все. Напоследок приведу весь задействованный код.
Полный код вызова асинхронной функции модуля обработки через БСП
Этот код можно просто скопировать в свою обработку. Модуль формы обработки:
Процедура ВыполнитьФункциюАсинхронно(Команда) ОбработчикОповещения = Новый ОписаниеОповещения("ПослеПомещенияФайлаНаСервер", ЭтотОбъект); ИмяФайла = ПолучитьИмяФайлаОбработки(); ОтправитьФайлНаСервер(ИмяФайла, ОбработчикОповещения); КонецПроцедуры Функция НачатьВыполнениеДлительнойОперации(ДвоичныеДанныеОбработки) ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияФункции(УникальныйИдентификатор); ПараметрыВыполнения.ВнешнийОтчетОбработка = ДвоичныеДанныеОбработки; ДлительнаяОперация = ДлительныеОперации.ВыполнитьФункцию(ПараметрыВыполнения, "ВнешняяОбработка."+ПолучитьИмяОбработки()+".МодульОбъекта.МояФункция"); // Если после второй точки не написано "МодульОбъекта" тогда БСП будет пытаться вызвать // экспортную функцию общего модуля или модуля менеджера объекта Возврат ДлительнаяОперация; КонецФункции Процедура ЗавершениеДлительнойОперации(РезультатВыполнения, ДополнительныеПараметры) Экспорт Если РезультатВыполнения = Неопределено Тогда Возврат; КонецЕсли; Если РезультатВыполнения.Статус = "Ошибка" Тогда СтандартныеПодсистемыКлиент.ВывестиИнформациюОбОшибке(РезультатВыполнения.ИнформацияОбОшибке); Возврат; КонецЕсли; Если РезультатВыполнения.Статус = "Выполнено" Тогда РезультатОперации = ПолучитьИзВременногоХранилища(РезультатВыполнения.АдресРезультата); // здесь результат УдалитьИзВременногоХранилища(РезультатВыполнения.АдресРезультата); КонецЕсли; КонецПроцедуры Процедура ОтправитьФайлНаСервер(ИмяФайла, ОбработчикОповещения) ПараметрыЗагрузки = ФайловаяСистемаКлиент.ПараметрыЗагрузкиФайла(); ПараметрыЗагрузки.ИдентификаторФормы = УникальныйИдентификатор; ПараметрыЗагрузки.Интерактивно = Ложь; ФайловаяСистемаКлиент.ЗагрузитьФайл(ОбработчикОповещения, ПараметрыЗагрузки, ИмяФайла); КонецПроцедуры Процедура ПослеПомещенияФайлаНаСервер(Результат, ДополнительныеПараметры) Экспорт Если Не ЗначениеЗаполнено(Результат.Имя) Тогда Возврат; КонецЕсли; ДвоичныеДанныеОбработки = ПолучитьИзВременногоХранилища(Результат.Хранение); ДлительнаяОперация = НачатьВыполнениеДлительнойОперации(ДвоичныеДанныеОбработки); // Во время выполнения длительной операции будет открыто окно с указанным заголовоком и текстом ПараметрыОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект); ПараметрыОжидания.Заголовок = "Пожалуйста, подождите"; ПараметрыОжидания.ТекстСообщения = "Идет выполнение длительной операции"; ПараметрыОжидания.ВыводитьСообщения = Истина; // если оставить "Ложь" (по умолч.), то // накопленные сообщения не будут выведены пользователю при возврате управления из // фоновой процедуры (функции) на клиент // После завершения длительной операции будет вызвано оповещение с текстом и пояснением ОповещениеПользователя = ПараметрыОжидания.ОповещениеПользователя; ОповещениеПользователя.Показать = Истина; ОповещениеПользователя.Текст = "Обработка завершена"; ОповещениеПользователя.Пояснение = "Выполнение длительной операции завершено"; ОписаниеОповещения = Новый ОписаниеОповещения("ЗавершениеДлительнойОперации", ЭтотОбъект); ДлительныеОперацииКлиент.ОжидатьЗавершение(ДлительнаяОперация, ОписаниеОповещения, ПараметрыОжидания); КонецПроцедуры Функция ПолучитьИмяФайлаОбработки() ОбъектОбработки = РеквизитФормыВЗначение("Объект"); Возврат ОбъектОбработки.ИспользуемоеИмяФайла; КонецФункции Функция ПолучитьИмяОбработки() Возврат РеквизитФормыВЗначение("Объект").Метаданные().Имя; КонецФункции Процедура НазначитьКлючУникальности() Если КлючУникальности = Неопределено Тогда ИмяОбработки = СтрРазделить(ЭтотОбъект.ИмяФормы, ".")[1]; КлючУникальности = ИмяОбработки + XMLСтрока(ТекущаяДата()); КонецЕсли; КонецПроцедуры Процедура ПриОткрытии(Отказ) НазначитьКлючУникальности(); КонецПроцедуры
Модуль обработки:
Функция МояФункция() Экспорт Для Инд = 1 По 1000000 Цикл // что-то делаем... КонецЦикла; Сообщить("Длительная процедура успешно выполнена"); Возврат 100500; КонецФункции
Для добавления комментария необходимо авторизоваться.
Вход | Регистрация