Фоновая обработка больших данных в 1С с прогресс баром

Пару лет назад уже сталкивался с подобной задачей (Фоновая обработка больших данных в 1С). Чуть погуглил, что изменилось за это время. А фактически ничего. До сих пор для того чтобы показать обычный прогресс бар, приходится использовать велосипеды. Временные хранилища, для того чтобы передать в клиента результат работы фоновой функции/процедуры как нельзя было использовать, так и сейчас нельзя. Прогресс бар, как нельзя было использовать без костылей…так и сейчас нельзя. Ну я конечно не смотрел что в БСП, т.к. зачастую приходится делать дописки где БСП или нет, или она древняя

Общая «шаблонная» схема использования фонового выполнения функций в 1С можно организаовать следующим образом:

  1. Запускаем фоновое задание
  2. На клиенте запускаем периодическое задание которое отлавливает вывод «сообщить» на сервере
  3. В фоновом задании при помощи «сообщить» выводим всякую служебную информацию. Если нужно в «клиент» передать данные — записываем временный файл в формате например json, и имя его, опять же при помощи «Сообщить» передаём на клиент.
  4. По окончании фонового задания, закрываем выполнение периодического задания

Запуск фонового задания:

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

Процедура или функция фонового задания обязательно должна находиться в общем модуле:

Функция СпарситьИсходныеДанныеНаСервер(параметры) экспорт;
...
Сообщить("Выполнено 10%");	
....
Сообщить("Выполнено 100%");
ИмяФайла=ПолучитьИмяВременногоФайла("json");
Текст = Новый ЗаписьТекста(ГдеИскать+ИмяФайла, КодировкаТекста.UTF8);
Текст.Записать(json_str);	
Сообщить("Результат:"+ИмяФайла);
...

Периодическая проверка и «отлов» серверного вывода «Сообщить»:

&НаКлиенте
Процедура ИндикаторВыполненияЗагрузки() Экспорт
	пр=ОпроситьФоновые();
	если пр<>неопределено тогда  
		объект.ИндВыполнения=пр;
		Состояние("Выполнено "+пр);
	конецесли;
КонецПроцедуры

&НаКлиенте
Процедура ИндикаторВыполненияЗагрузки() Экспорт
	пр=ОпроситьФоновые();
	если пр<>неопределено тогда  
		объект.ИндВыполнения=пр;
		Состояние("Выполнено "+пр);
	конецесли;
КонецПроцедуры

&НаСервере
Функция ПолучитьСообщенияФЗ(ФЗ, Состояние = Неопределено, УдалятьСообщения = Ложь) Экспорт
	Если Состояние = Неопределено Тогда
		Состояние = ФЗ.Состояние;
	КонецЕсли;
	МассивСообщений = Новый Массив;
	Сообщения = ФЗ.ПолучитьСообщенияПользователю(УдалятьСообщения);
	Если Сообщения <> Неопределено Тогда
		Для Каждого Сообщение Из Сообщения Цикл
			МассивСообщений.Добавить(Сообщение.Текст);
		КонецЦикла;
	КонецЕсли;
	Возврат МассивСообщений;
КонецФункции

&НаСервере
Функция ОпроситьФоновые()
	прог=неопределено;
	ФЗ = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(ЭтаФорма.ФоновоеИдентификатор);
	если ФЗ=Неопределено тогда
		ОтключитьОбработчикОжидания("ИндикаторВыполненияЗагрузки");
		возврат ложь;
	конецесли;	
	ФСообщения=ПолучитьСообщенияФЗ(ФЗ,,истина);
	Если ФСообщения.Количество() > 0 Тогда
		Для Каждого Сообщение Из ФСообщения Цикл
			Сообщить(Сообщение);
			если найти(Сообщение,"%")>0 тогда
				прог=Число(СтрЗаменить(СтрЗаменить(Сообщение,"Выполнено ",""),"%",""));			
			конецесли;
			если найти(Сообщение,"Результат:")>0 тогда
				ИмяФайла=СтрЗаменить(Сообщение,"Результат:","");
				Текст = Новый ЧтениеТекста(КаталогВременныхФайлов()+ИмяФайла, КодировкаТекста.UTF8);
				СтрокаJson = JsonВСтруктуру(Текст.Прочитать());	
ОтключитьОбработчикОжидания("ИндикаторВыполненияЗагрузки");
			конецесли;				
		КонецЦикла;
	КонецЕсли;
	возврат прог;
КонецФункции

В результате будет что-то вроде:

Фоновая обработка больших данных в 1С

Загрузка и обработка большого файла EXCEL в 1С с прогрессбаром

Задача: «фоново» загрузить файл эксель большого размера, с показом прогресса загрузки.

Решение:

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

Кроме того выплыла проблема с не рабочим способом передачи данных о загрузке при помощи хранения данных во «ВременныхХранилиах», описанным тут. Потому для получения прогресса воспользуемся возможностью зная идентификатор фонового процесса периодически получить с сервера данные выводимые при помощи «Сообщить()».

В общих модулях разместим следующий код фонового процесса:

&НаСервере
Функция ЗагрузкаФайлаПредварительная(АдресВременногоХранилищаФайла) Экспорт			
	ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресВременногоХранилищаФайла);
	ИмяВременногоФайлаСпр = ПолучитьИмяВременногоФайла("xlsx");
	ДвоичныеДанные.Записать(ИмяВременногоФайлаСпр);     
	возврат ИмяВременногоФайлаСпр;	
конецфункции	

Процедура ЗагрузкаФайлаДлительная(ИмяВременногоФайлаСпр) Экспорт			
		
    ТабличныйДокументСпр = Новый ТабличныйДокумент;
    ТабличныйДокументСпр.Прочитать(ИмяВременногоФайлаСпр,СпособЧтенияЗначенийТабличногоДокумента.Значение);
	
	//читаем все листы файла СправочникСМС
	Для Каждого ОбластьТД ИЗ ТабличныйДокументСпр.Области Цикл         		     
            ОбластьФайла = ТабличныйДокументСпр.ПолучитьОбласть(ОбластьТД.Имя);
            КолВоСтрокФайла = ОбластьФайла.ПолучитьРазмерОбластиДанныхПоВертикали();
            КолВоКолонокФайла = ОбластьФайла.ПолучитьРазмерОбластиДанныхПоГоризонтали();
            Сообщить("Строк:"+КолВоСтрокФайла);
            Сообщить("Колонок:"+КолВоКолонокФайла);         
            имялиста=ОбластьТД.Имя;
            Сообщить("Лист:"+имялиста);         
                    МассивИменКолонок=Новый Массив();
                    // читаем шапку листа
                    Для ит=1 ПО КолВоКолонокФайла Цикл
                        нКолонка = СтрЗаменить(ит, Символы.НПП, "");
                        ИмяКолонки=ОбластьФайла.ПолучитьОбласть("R3" + "C"+нКолонка).ТекущаяОбласть.Текст;                        
                        МассивИменКолонок.Добавить(ИмяКолонки);
					конеццикла; 					
					НачСтрока=3;КонСтрока=0;
                    НачСтрока = ?(НачСтрока = 0, 2, НачСтрока);
                    КонСтрока = ?(КонСтрока = 0, КолвоСтрокФайла, КонСтрока);
                    //перебираем все строки без первой строки  
					Индикатор=0;
                    Для нСтрокаТФ = НачСтрока+1 ПО КонСтрока Цикл
                        нСтрока = СтрЗаменить(нСтрокаТФ, Символы.НПП, "");                        
			если Окр(нСтрокаТФ*100/КонСтрока)<>Индикатор тогда
				Индикатор = Окр(нСтрокаТФ*100/КонСтрока);
				Сообщение = Новый СообщениеПользователю;
				Сообщение.Текст = "Выполнено %"+Индикатор;
				Сообщение.Сообщить();
			конецесли;
			Для нКолонкаТФ = 1 ПО КолВоКолонокФайла Цикл
                            нКолонка = СтрЗаменить(нКолонкаТФ, Символы.НПП, "");
                            Область = ОбластьФайла.ПолучитьОбласть("R"+нСтрока+"C"+нКолонка);
                            ТекущаяОбласть = Область.ТекущаяОбласть;
                            ЗначениеЯчейки = СокрЛП(ТекущаяОбласть.Текст);
			    ...
				обрабатываем файл
			    ...	
                        конеццикла;                         		
	конеццикла;
	Сообщение = Новый СообщениеПользователю;
	Сообщение.Текст = "Загрузка завершена.";
	Сообщение.Сообщить();

Клиентская часть в управляемых формах:

&НаКлиенте
Процедура Жмяк(Команда)
Режим = РежимДиалогаВыбораФайла.Открытие;
    ДиалогОткрытияФайла = Новый ДиалогВыбораФайла(Режим);
    ДиалогОткрытияФайла.ПолноеИмяФайла = "";
    Фильтр = НСтр("ru = 'Текст'; en = 'Text'")+ "(*.xls)|*.xlsx";
    ДиалогОткрытияФайла.Фильтр = Фильтр;
    ДиалогОткрытияФайла.МножественныйВыбор = ложь;
    ДиалогОткрытияФайла.Заголовок = "Выберите файлы";
    Если ДиалогОткрытияФайла.Выбрать() Тогда
        МассивФайлов = ДиалогОткрытияФайла.ВыбранныеФайлы;
		ФайлСМС="";
		Для Каждого ИмяФайла Из МассивФайлов Цикл
			ФайлСМС=ИмяФайла;
		конеццикла;
		если ФайлСМС<>"" тогда 			
					АдресХранилищаФайла = "";			
					Состояние("Перемещаю файл на сервер");
		            ПоместитьФайл(АдресХранилищаФайла, ФайлСМС, , Ложь, ЭтаФорма.УникальныйИдентификатор);   					
					Состояние("Обрабатывается файл "+ФайлСМС);							
					ЗапускФоновойЗагрузкиСправочника(АдресХранилищаФайла);					
					Состояние("Запущена фоновая обработка файла");
					ПодключитьОбработчикОжидания("ИндикаторВыполненияЗагрузки",1,ложь);	
		иначе
			сообщить("Файл не выбран");
		конецесли;	
	конецесли;	        

КонецПроцедуры


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

&НаСервере
Функция ОпроситьФоновые()
	прог=неопределено;
	ФЗ = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(ЭтаФорма.ФоновоеИдентификатор);
	ФСообщения=ПолучитьСообщенияФЗ(ФЗ,,истина);
	Если ФСообщения.Количество() > 0 Тогда
		Для Каждого Сообщение Из ФСообщения Цикл
			Сообщить(Сообщение);
			если найти(Сообщение,"%")>0 тогда
				Этаформа.Прогресс=СтрЗаменить(Сообщение,"Выполнено %","");			
				прог=Этаформа.Прогресс;
			конецесли;
		КонецЦикла;
	КонецЕсли;
	возврат прог;
КонецФункции
&НаКлиенте
Процедура ИндикаторВыполненияЗагрузки() Экспорт
	пр=ОпроситьФоновые();
	если пр<>неопределено тогда
		Состояние("Выполнено "+пр);
	конецесли;
КонецПроцедуры

Запуск скрипта с продолжением работы после закрытия терминала..

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

Для запуска скрипта без связи с сессией пользователя следует использовать утилитуnohup:

В этом случае запущенный скрипт останется работать даже при отключении от сервера, но весь выводимый скриптом текст будет записываться в файл nohup.log, создаваемый в текущем каталоге, о чем nohup непосредственно и информирует сообщением nohup: appending output to nohup.out. При этом данный файл будет создан даже в том случае, если у скрипта никогда не будет никакого вывода.