В процессе разработки на платформе 1С:Предприятие 8 часто возникает вопрос о гарантиях порядка выполнения программного кода. Типичная ситуация: разработчик создает клиентскую процедуру, внутри которой поочередно вызываются несколько серверных процедур. Возникает логичное опасение — может ли случиться так, что вторая или четвертая процедура начнет работу до завершения первой? Рассмотрим этот вопрос детально, разберем механизмы работы платформы и выясним, какие нюансы могут создать иллюзию нарушения последовательности.
Начнем с основ. По умолчанию в 1С вызовы серверных процедур и функций из клиентского контекста являются синхронными. Проанализируем, что это означает на практике для потока выполнения. Когда мы пишем код в модуле формы:
&НаКлиенте
Процедура ЗапуститьОбработкуДанных()
// Шаг 1: Подготовка данных
ПроцедураСервера1();
// Шаг 2: Расчет показателей
ПроцедураСервера2();
// Шаг 3: Формирование движений
ПроцедураСервера3();
КонецПроцедуры
Рассмотрим по шагам, как ведет себя система. В момент вызова ПроцедураСервера1() основной поток клиентского приложения приостанавливается. Пользователь видит "песочные часы" или просто не может взаимодействовать с интерфейсом. Клиент ждет, пока сервер полностью отработает весь код внутри первой процедуры, выполнит все запросы к базе данных и вернет управление. Только после того, как ПроцедураСервера1 завершится, интерпретатор перейдет к строке с ПроцедураСервера2(). Таким образом, строгая последовательность гарантирована архитектурой платформы.
Почему же у разработчиков иногда возникает ощущение, что данные во второй процедуре еще "не готовы", хотя первая уже отработала? Разберем основные технические причины, которые могут привести к такому выводу.
1. Транзакции и уровни изоляции. Рассмотрим ситуацию, когда ПроцедураСервера1 записывает документ или набор записей регистра. В 1С любая запись объекта инициирует транзакцию. Если внутри серверной процедуры не было явной ошибки, транзакция фиксируется (commit) в конце выполнения серверного вызова. Однако, если первая процедура делает запись, а вторая пытается прочитать эти данные, важно учитывать, происходят ли эти вызовы в рамках одного серверного контекста (одного сеанса) или разных. В рамках одного сеанса последовательность соблюдается, но если между процедурами происходит физический разрыв или используются механизмы, работающие вне основной транзакции, данные могут быть "невидимы" для СУБД на доли секунды.
2. Кэширование объектов. Это одна из самых частых причин "странного" поведения кода. Выясним причину: если в ПроцедураСервера1 вы изменили объект (например, справочник), а в ПроцедураСервера2 обращаетесь к этому же объекту через переменную, созданную ранее, вы можете получить данные из локального кэша, а не из актуальной базы данных. Разберем пример:
&НаСервере
Процедура ПроцедураСервера1(СсылкаНаОбъект)
Объект = СсылкаНаОбъект.ПолучитьОбъект();
Объект.Реквизит1 = "Новое значение";
Объект.Записать();
КонецПроцедуры
&НаСервере
Процедура ПроцедураСервера2(СсылкаНаОбъект)
// ОШИБКА: Если мы не перечитаем данные, можем увидеть старое значение
Сообщить(СсылкаНаОбъект.Реквизит1);
КонецПроцедуры
Для решения этой проблемы необходимо всегда использовать актуальные ссылки или принудительно перечитывать объекты, если логика программы предполагает их последовательное изменение в разных процедурах.
Если нам действительно нужно, чтобы процедуры выполнялись параллельно, мы используем механизм фоновых заданий. Проанализируем ситуацию: если внутри ПроцедураСервера1 разработчик напишет следующий код:
ФоновыеЗадания.Выполнить("МойМодуль.ДлительнаяОперация", Параметры);
В этом случае ПроцедураСервера1 инициирует запуск нового процесса на сервере и мгновенно завершится, вернув управление на клиент. Клиент тут же запустит ПроцедураСервера2. В итоге "ДлительнаяОперация" и ПроцедураСервера2 будут выполняться одновременно. Если ваша логика во второй процедуре опирается на результат выполнения фонового задания из первой — вы получите ошибку или пустой результат, так как фоновый процесс, скорее всего, еще не успеет завершиться.
С развитием платформы 1С появились асинхронные методы, работающие через обещания (Promises). Посмотрим на пример кода, который может нарушить привычную последовательность:
&НаКлиенте
Асинхронная Процедура ЗапуститьВызовы()
// Если вызвать асинхронный метод без "Ждать", код пойдет дальше
ВыполнитьЗадачуАсинхронно();
ПроцедураСервера2(); // Выполнится, не дожидаясь завершения первой!
КонецПроцедуры
Чтобы сохранить последовательность в асинхронном контексте, мы должны обязательно использовать ключевое слово Ждать (Await). Это заставит систему дождаться результата выполнения первой функции перед переходом к следующей строке.
Рассмотрим еще один важный аспект, упомянутый в обсуждении — вложенность. Если ПроцедураСервера1 внутри себя вызывает ПроцедураСервера4, то порядок выполнения будет следующим:
ПроцедураСервера1.ПроцедураСервера4.ПроцедураСервера1.ПроцедураСервера2.Здесь работает классический принцип стека (LIFO). Никакого "перемешивания" или одновременного выполнения кода разных процедур в одном потоке не происходит.
Проанализируем шаги, которые помогут вам избежать проблем с последовательностью и целостностью данных:
НачатьТранзакцию() и ЗафиксироватьТранзакцию().ФоновыеЗадания.ОжидатьЗавершения()), прежде чем переходить к процедурам, использующим их результат.Объект.Прочитать() или заново получайте объект из базы, чтобы исключить влияние кэша.Резюме: В стандартном синхронном коде 1С выполнение процедур всегда идет строго по порядку, строка за строкой. Если вы наблюдаете иное поведение, проверьте код на наличие фоновых заданий, асинхронных вызовов без ожидания или проанализируйте работу с кэшем объектов.