Многопоточность в 1С, клиент-серверный вариант

Многопоточность — это свойство системы выполнять одновременно более одной операции (в пределе — более одной программы), позволяя в ряде случаев добиться существенного роста производительности программного продукта…

Пролог.

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

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

Введение.

Итак, многопоточность — это возможность выполнять несколько программ (потоков, threads) одновременно. Клиент-серверная архитектура 1С позволяет реализовать данную функцию через механизм фоновых заданий, при этом можно добиться существенного повышения производительности решений на платформе 1С.

Многопроцессорные сервера 1С являются устоявшимся фактом. Загрузка процессоров на серверах даже при достаточно интенсивной работе пользователей не редко держится на уровне 20-30%, т.к. клиентская часть 1С берет на себя функционал как минимум по отображению форм и, возможно, какую-то дополнительную нагрузку. В толстом же клиенте нагрузка на нее еще выше. Отсюда следует, что на серверах 1С существует неиспользованный вычислительный ресурс, который можно утилизировать.

Как это работает?

Для того, чтобы заставить 1С что-то делать параллельно, нужно обрабатываемые данные разбить на куски и постепенно скармливать создаваемым для этого потокам. Архитектурно это состоит из механизма получения порции данных — источника данных, механизма обработки данных — рабочей процедуре, и управляющей функции. 

В итоге у нас вырисовывается такая схема:

Управляющая процедура -> Поставщик данных (запрос)  -> Фоновое задание (порция данных) -> Отчет о выполнении -> Управляющая процедура.

Математическая модель будет выглядеть так:

  1. Получаем данные для обработки.
  2. Задаем количество фоновых заданий.
  3. Создаем массив для хранения фоновых заданий.
  4. Если количество стартованных фоновых заданий меньше, чем задано — п.5., иначе п.7.
  5. Стартуем фоновое задание и передаем ему оговоренное количество данных.
  6. Еще есть данные? Да — п.4, нет — выход (или подождем через ОжидатьЗавершения(МассивФЗ), после чего выйдем).
  7. Проверяем, есть ли в массиве задание, которое уже отработало. Если есть — п.5., иначе п.7.

Практическая задача.

Допустим, нам надо заполнить новый реквизит регистра сведений в соответствии с определенными условиями, которые могут быть отбором компоновки данных.

Дано:

  1. Регистр сведений «СферическиеКони» с измерением «Конь» (справочник «Кони») и ресурсом «ПлотностьСреды» (число 15,10) .
  2. Мы добавили параметр «СопротивлениеСреды», характеризующий сопротивление среды при максимальной скорости данного коня. Именно его и надо заполнить.

Предположим, что коней у нас много и заполнение без многопоточности займет много времени.

Опишем для начала рабочую процедуру:

Процедура ЗаполнитьСопротивлениеСреды(ИсточникДанных) Экспорт
Для Каждого Ст ИЗ ИсточникДанных Цикл
Рег = РегистрыСведений.СферическиеКони.СоздатьМенеджерЗаписи();
Рег.Конь = Ст.Конь;
Рег.Прочитать();
Рег.СопротивлениеСреды = ПолучитьСопротивлениеСредыДляКоня(Ст.Конь.МаксСкорость, Ст.Конь.Радиус, Рег.ПлотностьСреды);
Рег.Записать();
КонецЦикла;
КонецПроцедуры

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


Теперь опишем управляющую процедуру:

В принципе, управляющая процедура может и не быть процедурой — она может продолжить код получения данных

  МассивФЗ = Новый Массив;
КоличествоЗаданий = 10;
КоличествоДанныхДляЗадания = 100;
МассивДанныхЗадания = Новый Массив;
Для Каждого Ст ИЗ ИсточникДанных Цикл
МассивДанныхЗадания.Добавить(Ст);
Если МассивДанныхЗадания.Количество() = КоличествоДанныхДляЗадания Тогда
Пока МассивФЗ.Количество() = КоличествоЗаданий Цикл
Для Каждого ФЗ ИЗ МассивФЗ Цикл
// не помню, как получается задание, но как-то так - нет под рукой 1С
// смысл в том, чтобы проверить статус у запущенного задания, и если он не активен - освободить место в массиве ФЗ
Если НЕ ФоновыеЗадания.ПолучитьФоновоеЗаданиеПоГуид(ФЗ).Статус = СтатусыФЗ.Активно Тогда
МассивФЗ.Удалить(МассивФЗ.Найти(ФЗ));
Прервать; // выйдем из "бесконечного" цикла, когда хотябы одно из заданий закончилось
КонецЕсли;
КонецЦикла; // ФЗ
КонецЦикла; // КоличествоФЗ
МассивПараметров = Новый Массив;
МассивПараметров.Добавить(МассивДанныхЗадания);
ФЗ = ФоновыеЗадания.Выполнить("ОбщийМодуль.ЗаполнитьСопротивлениеСреды", МассивПараметров);
МассивФЗ.Добавить(ФЗ.ГУИД);
МассивДанныхЗадания = Новый Массив;
КонецЕсли; // КоличествоДанныхДляЗадания
КонецЦикла;
Если МассивДанныхЗадания.Количество() > 0 Тогда
МассивПараметров = Новый Массив;
МассивПараметров.Добавить(МассивДанныхЗадания);
ФЗ = ФоновыеЗадания.Выполнить("ОбщийМодуль.ЗаполнитьСопротивлениеСреды", МассивПараметров);
КонецЕсли;

Собственно, что мы сделали? Создали массив заданий и определили параметры обработки (количество потоков, количество данных в потоке). Далее заполнили массив данными, проверили, есть ли свободное место в массиве, если нет — дождались, и передали в фоновое задание для обработки. После чего поместили ГУИД ФЗ в массив.


Заключение.

Многопоточные вычисления все больше будут использоваться в дальнейшем, т.к. процессорная архитектура достигла пределов роста вверх по частоте — теперь она растет вширь по количеству ядер, объему памяти, емкости накопителей. Все это должно привести к прогрессу в области параллельных вычислений.

50 Comments

  1. andy23

    Регистр «СферическиеКони» звучит очень даже не плохо! А многопоточность тема хорошая, актуальная.

    Reply
  2. starik-2005

    (1) andy23, как показывает практика, статьи о ней (многопоточности) и 1С появились аж в 2008-м году (то, что я смог найти — может что и раньше есть). Но и сейчас применение данного механизма доступно небольшому кругу посвященных ))

    Reply
  3. insurgut

    А по факту многопоточность так же внутри одного ядра выполняется?

    Reply
  4. fancy

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

    Reply
  5. WKBAPKA

    (4) fancy, это же пример. а по факту потом можно оптимизировать и оптимизировать. Автору спасибо за статью!

    Reply
  6. DoctorRoza

    Предложенная идея была озвучена в курсе по Оптимизации Бурмистрова Александра сколько — то там месяцев назад. Ничего нового, чего-то там записать в независимый РС несколькими ФЗ. При имеющейся архитектуре 1С да и перечню решаемых задач (ну рассчитайте себестоимость в несколько потоков!) многопоточность пока неактуальна!

    Reply
  7. starik-2005

    (3) insurgut, в действительности фоновое задание запускается отлельным потоком на сервере 1с. Дальше потоком рулит ОС, выделяя ему память и ядро. Если процессорных ядер больше одного — происходит реальное распараллеливание, но даже на одном ядре код нескольких потоков может выполняться быстрее, т.к. waitы и sleepы — задержки — позволяют другим потокам в это время чтото делать.

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

    Reply
  8. insurgut

    (7) если не ошибаюсь (что не исключено ;)), один процесс RPHOST выполняется на 1 ядре. И получается все зависит от того, какой RPHOST (если их несколько) будет выполнять тот или иной поток. Ситуация, что все они будут выполнятся на одном и том же рпхосте не исключена. И беда в том, что повлиять на это возможности уже нет (конечно и тут я могу ошибаться, поправьте если что). Но в любом случае, как вы сами и указали, все многопоточность сведется к одной последовательной операции чтения с диска. Но тут на помощь нам идет сам SQL, который возможно заранее все уже выгрузил в память.

    P.S. Очень интересно было бы увидеть данные по приросту скорости выполнения при использовании приведенного метода многопоточности на реальной базе 🙂

    Reply
  9. pscorp

    (8) rphost выполняется не на одном ядре, он как раз может утилизировать все ядра. То, что это так можно убедиться запустив сервер 1с с одним процессом и увидев, что нагрузка попадает на все ядра. Из-за убеждения, что один процесс использует только одно ядро многие рекомендуют запускать рабочих процессов по числу ядер. На курсе 1с:Эксперт про это как раз рассказывают.

    Reply
  10. starik-2005

    (8) insurgut, Вам, конечно, уже ответили, но я могу несколько дополнить. В случае вычисления в фоновом задании выражения на встроенном языке без обращения к ресурсам сервера SQL вы ограничены ресурсами сервера 1С, но если у происходит обращение к серверу СУБД — картина меняется. Т.к. АДО-механизм использует задержки при обращении к серверу, ожидая ответ, то второй поток даже на одном ядре может инициировать в это время еще одно соединение с сервером СУБД, утилизируя его вычислительные ресурсы. Отсюда мораль: если бОльшую часть работы мы перенесем на сервер СУБД, то можно запустить больше потоков на сервере 1С.

    Reply
  11. Jen1978

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

    Пришлось как-то изучить Java. Только самые азы. Так вот там все построено на многопоточности.

    Reply
  12. genayo

    (6 Более того, я например сейчас работаю со специализированной конфой, написанной где-то в 2011 — 2012 году, где эта технология применяется в полный рост…

    Reply
  13. amon_ra

    А если взять в расчет файловую базу, то там как многопоточность организовывать? Через обработчик ожидания? Серверными все, конечно, понятно, а вот как обстоит дело с файловыми? Кто-нибудь пробовал?

    Reply
  14. starik-2005

    (13) amon_ra, вряд ли при чтении из файловой базы удастся добиться ускорения при многопоточности, т.к. все упрется в клиентскую машину (ОС, диски, ядра, …). Если ядер много, то что-то вычислить на встроенном языке без обращения к БД будет быстрее, но для этого уже механизм фоновых заданий вряд ли подойдет.

    Reply
  15. vde69

    вот тут обсуждал многопоточность http://www.forum.mista.ru/topic.php?id=743705

    там даже с примером обратной связи от всех потоков….

    Reply
  16. starik-2005

    (15) vde69, связи потоков и управляющей процедуры реализуются через метод «Сообщить» в ФЗ и функцию чтения сообщений у текущего потока. В описании схемы я как раз указал, что ФЗ может передавать данные управляющей процедуре, но в мат.модель и код я этого не добавил. Могу исправить и опубликовать статью о том, как это сделать (типа «прогресс-бар для мультипоточных вычислений») ))

    Reply
  17. dusha0020

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

    Reply
  18. starik-2005

    (17) dusha0020, все зависит от того, что Вы собираетесь с этим делать. «В оригинале» для синхронизации многопоточных вычислений применяются блокировки (mutex), позволяющие собирать результаты функций в некотором узле. Вопрос только в том, что далать с результатами. Если писать в регистр -то проще делать это сразу в потоке, если использовать для последующих вычислений, то, в принципе, для не сильно объемных данных писать через «Сообщить(ЗначениеВСтрокуВнутр(Результат))» в ФЗ, откуда уже разворачивать через «Для каждого Сообщение ИЗ ФЗ.ПолучитьСообщения() Цикл массивРезультатов.Добавить(ЗначениеИЗСтрокиВнутр(Сообщение.Текст)) КонецЦикла». В принципе, это уже тема для новой статьи — могу написать с примерами.

    Reply
  19. spezc

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

    Reply
  20. starik-2005

    (19) spezc, обоснуйте свою позицию. У нас в организации многопоточность применяется от пакетного формирования печатных форм механизма автоматической печати и до обмена данными. Везде это приводит к весьма существенному улучшению производительности. Например, формирование печатных форм вместо трех часов занимает 50 минут (при пяти потоках).

    Reply
  21. karapuzzzz
    Reply
  22. dusha0020

    (18) Про писать в регистр и чтение сообщений ФЗ я знаю:) Интересовался более изящными способами в контексте 1С.

    Reply
  23. starik-2005

    (22) dusha0020, а чем способ с сообщениями ФЗ не понравился? Вы до конца дочитали?

    Reply
  24. rtnm

    Управляющая функция жестко зациклена — не стоит демонстрировать такой код.

    Reply
  25. starik-2005

    (24) rtnm, да что Ви говорите ))) Не стоит писать то, в чем Ви не понимаете )))

    Reply
  26. starik-2005

    (24) rtnm, уважаемый, я, конечно, понимаю, что не все специалисты — хорошие. Но не стоит свои личные проблемы понимания чужого кода принимать близко к сердцу. Будьте проще и люди к Вам потянутся (с) )))

    Reply
  27. starik-2005

    (25) для тех, кто все же не понял, как мы выходим из цикла, на всякий случай внес комментарии в код. Г-н rtnm, будучи хорошим специалистом, совершенно верно подметил, что цикл так просто не кончается. Но, слава Богу, есть программисты и получше. В данном конкретном случае цикл завершается оператором «прервать» после удаления первого отработавшего задания из массива фоновых заданий. После этого количество заданий будет больше, чем количество элементов массива, что приведет к выходу и из второго цикла.

    Есть другой путь — процедура ОжиданиеЗавершения(массивФЗ). Но тогда мы теряем асинхронность. Мы запустили 10 заданий, они могут выполняться разное время, но ожидая завершения всех заданий мы теряем в производительности. надеюсь, теперь всем все понятно.

    Reply
  28. rtnm

    (27) вообще, речь о том, что код управляющей процедуры зря тратит процессорное время, гоняя свой цикл без устали.

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

    Reply
  29. starik-2005

    (28) rtnm, ну минусы-то Вы сильны ставить, а вот разобраться с проблемой не хотите, получается. Вот Вам ссылка:

    http://www.forum.mista.ru/topic.php?id=626984

    // Старый добрый WaitForMultipleObjects() в исполнении 1С вышел не айс. //

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

    Reply
  30. rtnm

    (29) минус вам за ваш слог в комментариях. Если бы вы в статье написали, что ФоновыеЗадания.ОжидатьЗавершения (с указанным таймаутом) работает как-то не очень, то моих комментариев вы бы и не увидели. По существу, вы проверяли что ФоновыеЗадания.ОжидатьЗавершения (с указанным таймаутом) сейчас работает как-то не очень?

    Reply
  31. starik-2005

    (30) rtnm, во-первых, какое задание мы ожидаем? Все? Если все, то может так оказаться, что одно из заданий будет работать в 2 раза дольше, чем остальные. В итоге мы теряем вычислительные ресурсы сервера. И в 8.2. действительно до сих пор наблюдаются проблемы с работой ожидания завершения.

    Во-вторых, использование бесконечного цикла не является моветоном в программировании. Читайте тут: https://ru.wikipedia.org/wiki/%D0%91%D0%B5%D1%81%D0%BA%D0%BE%D0%BD%D0%B5%D1%87%D0­%BD%D1%8B%D0%B9_%D1%86%D0%B8%D0%BA%D0%BB

    /////////////////

    Роль бесконечных циклов в Тьюринг-полноте языков[править | править вики-текст]

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

    Любая программа может быть написана при помощи:

    бесконечных циклов;

    команд выхода из цикла;

    операторов ветвления (if-then);

    последовательностью команд, исполняемых одна после другой;

    Примечание: обратите внимание, что универсальный оператор GOTO (безусловный переход) не нужен, а достаточно специального оператора выхода из цикла, при этом, в случае n вложенных циклов потребуется n специальных операторов выхода из цикла (кроме небольшого количества языков с расширенным синтаксисом операторов выхода) или один оператор GOTO и одна метка.

    /////////////////////////////

    rtnm. учите мат.часть — это очень полезно. И запомните, если Вас всегда хвалят и всегда с Вами согласны — вы не развиваетесь. Я объективную развернутую критику крайне приветствую, но Ваше «Управляющая функция жестко зациклена — не стоит демонстрировать такой код.» просто показывает, что в программировании, увы, Вы ничего не смыслите. Уж извините, но как есть.

    Reply
  32. rtnm

    Спасибо огромное, теперь то я все понял!!!

    Reply
  33. dusha0020

    (23) Дочитал до конца, но давайте подумаем о практической пользе многопоточности. Например, если я выделяю некие промежуточные расчеты и собираю промежуточные результаты в параллельных потоках. Если промежуточный результат не велик и не сложен, то для большого объема вычислений нужно создать сотни и тысячи фоновых заданий, получить результаты и собрать их вместе. А параллельность выражается не количеством потоков, а количеством процессоров, то есть от такого деления реальной пользы будет не много за вычетом накладных расходов по сборке — разборке заданий и результатов.

    Мне кажется правильнее делить задачу на 6-10 больших фрагментов и получать результаты большими порциями. Обработка 6-10 фрагментов в одном потоке, это, согласитесь, не обработка тысяч мелких ответов. Но вот в состоянии ли ЗначениеВСтроку() перенести ТЗ из 50 000 строк и 10-12 колонок числовых данных?

    Reply
  34. starik-2005

    (33) dusha0020, не совсем верно. хотя тоже имеет право на существование. Накладные расходы на создание потока в принципе существенны, и при пустом цикле от 1 до даже 1000000 ощутимой разницы не будет, но при проведении документов, если регистры заполняются таким образом, что исключаются взаимоблокировки (читайте «библию 1С» — там об этом подробно написано, да и вообще в литературе по разработке приложений, использующих СУБД все это есть), выигрыш от мультипоточности весьма велик. Для нас оптимальным было 30-50 документов и 8-12 потоков. Средняя скорость проведения документа было примерно 0,1 сек против 0,9-1,1 секунды в один поток.

    Reply
  35. engineer74

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

    Reply
  36. starik-2005

    (35) engineer74, а в чем проблема?

    Reply
  37. engineer74

    (36) Может,уже обсуждали, Если фоновое задание зависло — как идентифицировать зависшее задание?

    Reply
  38. starik-2005

    (37) engineer74, ну тут множество разных способов. Но важно выяснить, почему зависает задание. Причин тоже множество:

    — запуск внешнего приложения — ФЗ будет ждать, пока оно завершит работу.

    — запрос — ФЗ будет ждать, пока возвратятся данные.

    — COM-объект — ФЗ будет ждать ответа.

    — 1С тоже может повесить задание, но я таких примеров не встречал.

    Reply
  39. engineer74

    (18) Да с примерами интересна тема «способ с сообщениями ФЗ»

    Reply
  40. starik-2005

    (39) engineer74, да, можно писать через сообщить(текст) в ФЗ. Если ща последнюю минуту не было сообщений — ФЗ зависло. У нас так ФЗ в регистр пишет, если время определенное прошло — система убивает задание и стартует новое, после чего чистит регистр от старого задания и размещает там информацию о новом. При старте ФЗ ему передается ключ, по которому оно себя в регистре ищет.

    Кстати, ФЗ может иногда отваливаться — вместе с убиваемым менеджером кластера rphost’ом. У нас так печать работает в 5 потоков на Linux-сервера печати на базе CUPS. Т.к. они постоянно вылетали, и до включения полнейшего лога сервака мы не понимали, почему. В итоге сделали регистр, куда клались данные, отсортированные по потокам, и каждые 10 минут запускали задание, которое проверяло, есть ли фоновые задания с определенным именем. Если нет, и в регистре не пусто — запускало по новой и отправляло письмо админам. В итоге нашли проблему — одновременное подключение внешней компоненты печати ШК. Обернули в «мутекс» — все стало стабильно. Как-то отключили «допечатывание» — 2 недели проработало без этого — даже не заметили. Ни одно задание не отвалилось, ни один rphost не умер.

    Reply
  41. KroVladS

    (0)

    тема на инфостарте 2013г http://infostart.ru/public/182139/

    книга знаний на мисте 2008г http://kb.mista.ru/article.php?id=696

    а вообще очень полезная вещь, если кому то поможет пусть лучше дубли будут.

    Reply
  42. KroVladS

    (8) insurgut,

    P.S. Очень интересно было бы увидеть данные по приросту скорости выполнения при использовании приведенного метода многопоточности на реальной базе 🙂

    реальные результаты http://forum.infostart.ru/forum24/topic83924/message889711/#message889711

    Reply
  43. starik-2005

    (41) KroVladS, да, о многопоточности пока не очень много статей. Так что новыми статьями в принципе продвигаем технологию, актуализируя «бренд» ))) Много — не мало!

    Reply
  44. starik-2005

    (41) KroVladS, в принципе, отличие этой статьи в том, что здесь описывается технология скармливания пакетов с балансировкой нагрузки. Т.е. функции получения данных и их обработки разделены. Данные получаются потоком, достигая некоторого предела передаются в фоновое задание для обработки, при этом количество ФЗ ограничено определенным значением. Другими словами, здесь выделен отдельно управляющий заданиями механизм. В следующей статьея рассматриваю технологию создания «мьютекса» — в конкретном варианте — механизма выделения блока монопольного исполнения (critical code)

    Reply
  45. avto1c

    (6) DoctorRoza, не актуальна пока вы не группа компаний по 20 — 50 пользователей в каждой. В противном случае появляются задачи для которых есть возможность и необходимость распараллеливания вычислений.

    Reply
  46. alex_4x

    1С Сделали работу с потоками на «отъ-ись» Типа реализовано, а то что хреново и не работает — это уже мелочи.

    Должно быть как в NET —

    1. определил спектр видимости переменных (в каждом потоке свой экземплар, или указанные потоки работают с одними и теми же переменными)

    2. определил функции или процедуры, которые будут запускаться

    3. запустил параллельно процедуры, с возможностью передачи управления по исключению или если всё хорошо (continuous)

    4. ожидаем завершения нужных процедур (не в цикле!!!)

    Reply
  47. starik-2005

    (46) alex_4x, есть функция ОжидатьЗавершения, в качестве аргументов передается список фоновых заданий. Чем плохо? .NET на клиенте выполняет многопоточный код, а 1С на сервере. Реализация вполне нормальная, способы обмена данными с потоком в принципе реализуемы. остается уточнить, какую именно задачу Вы хотите решить, и из этой предметной позиции уже говорить о сильных и слабых сторонах реализации в той или иной системе.

    Reply
  48. DarkAn

    (17) Если вопрос об обмене данных еще актуален — http://infostart.ru/public/626117/

    Reply
  49. triviumfan

    Сергей, не подскажете, а как получать процент выполнения задания или заданий в обычном приложении?

    Куда обычно скидывают состояния? Отдельную таблицу создавать как-то некрасиво. Хранилище общий настроек?

    Reply
  50. triviumfan

    (49) Разобрался. У фонового задания есть метод «ПолучитьСообщенияПользователю()», который получает то, что можно сообщить с помощью объекта «СообщениеПользователю».

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *