Контекст выполнения в 1С 8.3: инструкции препроцессора, сеанс и фоновые задания

· 9 мин

Содержание

Частое заблуждение — искать в 1С встроенную функцию вроде ЭтоСервер(). Такой функции в платформе нет. Проверить, на клиенте или на сервере выполняется код, можно инструкцией препроцессора #Если Сервер Тогда … #КонецЕсли, а тип сеанса (тонкий клиент, фоновое задание, COM-соединение) — через ПолучитьТекущийСеансИнформационнойБазы().ИмяПриложения.

Первое отвечает на вопрос «в каком контексте скомпилирована эта строка». Второе — «кто вообще запустил сеанс». Это разные вещи, и путаница между ними даёт большинство ошибок с объектами на клиенте. Всё ниже относится к платформе 8.3 и управляемому приложению; отличия обычного приложения отмечены отдельно.

Почему в 1С нет «ЭтоСервер» и как его заменить

В управляемом приложении один и тот же модуль может компилироваться дважды: отдельно для клиента и отдельно для сервера. Поэтому вопрос «где я?» решается не в момент выполнения, а в момент компиляции. Для этого служат символы препроцессора.

Основные символы, которые понимает #Если:

  • 🖥 Клиент / НаКлиенте — любой клиентский контекст;
  • 🗄 Сервер / НаСервере — серверный контекст;
  • 🪶 ТонкийКлиент — тонкий клиент;
  • 🌐 ВебКлиент — веб-клиент в браузере;
  • 🧱 ТолстыйКлиентУправляемоеПриложение и ТолстыйКлиентОбычноеПриложение — толстый клиент в двух режимах;
  • 🔌 ВнешнееСоединение — COM-соединение;
  • 📱 МобильноеПриложениеКлиент, МобильноеПриложениеСервер — мобильная платформа.

Символы можно комбинировать через И, ИЛИ, НЕ. Если нужна привычная функция, сделайте её сами в общем модуле, у которого включены флажки «Клиент (управляемое приложение)» и «Сервер»:

// Общий модуль ОбщегоНазначенияКлиентСерверСвой
Функция ЭтоСерверныйКонтекст() Экспорт

	#Если Сервер Тогда
		Возврат Истина;
	#Иначе
		Возврат Ложь;
	#КонецЕсли

КонецФункции

Функция ЭтоВебКлиент() Экспорт

	#Если ВебКлиент Тогда
		Возврат Истина;
	#Иначе
		Возврат Ложь;
	#КонецЕсли

КонецФункции

Модуль компилируется в обоих контекстах, и в каждом остаётся своя ветка. На клиенте функция вернёт Ложь, в серверной процедуре — Истина.

В файловом варианте серверный код физически выполняется в том же процессе, что и клиент. Но символ Сервер всё равно истинен только в серверном контексте. Проверка препроцессором ведёт себя одинаково в файловой и клиент-серверной базе — на это и стоит опираться.

Директивы компиляции: где окажется объект

Второй уровень — директивы перед процедурами в модулях форм и команд. Они не проверяют контекст, а задают его.

ДирективаГде выполняетсяДоступ к данным формы
&НаКлиентеклиентда, клиентские данные формы
&НаСервересерверда, форма передаётся на сервер
&НаСервереБезКонтекстасервернет, только параметры
&НаКлиентеНаСервереБезКонтекстатам, откуда вызвалинет

Отсюда прямой ответ на вопрос из заголовка про объект. Прикладной объект — СправочникОбъект, ДокументОбъект и подобные — живёт только на сервере. На клиенте в форме вместо него лежат ДанныеФормыСтруктура. Чтобы получить настоящий объект, нужна серверная процедура и РеквизитФормыВЗначение("Объект"), а для записи обратно — ЗначениеВРеквизитФормы.

Ссылки (СправочникСсылка, ДокументСсылка) доступны и на клиенте, и на сервере. Но прочитать через точку реквизит ссылки на тонком клиенте нельзя: за данными нужно сходить на сервер.

&НаКлиенте
Процедура ПроверитьКонтрагента(Команда)
	Если ИННЗаполнен(Объект.Контрагент) Тогда
		Сообщить("ИНН есть");
	КонецЕсли;
КонецПроцедуры

&НаСервереБезКонтекста
Функция ИННЗаполнен(Контрагент)
	Возврат ЗначениеЗаполнено(Контрагент.ИНН);
КонецФункции

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

Кто запустил сеанс: ИмяПриложения и СистемнаяИнформация

Препроцессор знает контекст строки кода, но не знает, что в серверном коде сейчас работает фоновое задание или внешнее соединение. Для этого есть свойство сеанса.

// Только серверный контекст
Функция ТипСеанса() Экспорт
	Сеанс = ПолучитьТекущийСеансИнформационнойБазы();
	Возврат Сеанс.ИмяПриложения;
КонецФункции

Типичные значения, которые вернёт ИмяПриложения:

ЗначениеКто подключился
1CV8Cтонкий клиент
1CV8толстый клиент
WebClientвеб-клиент
Designerконфигуратор
COMConnectionвнешнее соединение
BackgroundJobфоновое задание
WSConnectionвеб-сервис
HTTPServiceConnectionHTTP-сервис

ПолучитьТекущийСеансИнформационнойБазы() доступна только на сервере. С клиента её зовут через серверную функцию без контекста.

На клиенте полезен объект СистемнаяИнформация. Свойство ТипПлатформы показывает операционную систему и разрядность, ВерсияПриложения — номер платформы, ИнформацияПрограммыПросмотра — строку браузера в веб-клиенте.

&НаКлиенте
Процедура ПоказатьОкружение(Команда)
	Инфо = Новый СистемнаяИнформация;
	Сообщить(СтрШаблон("Платформа %1, ОС %2", Инфо.ВерсияПриложения, Инфо.ТипПлатформы));
КонецПроцедуры

Стек вызовов: как понять, откуда пришёл вызов

Встроенной функции, которая вернула бы стек вызовов прямо в коде, в языке нет. Поэтому «на клиенте или на сервере упало» смотрят двумя способами.

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

Подробное представление ошибки. Текст из ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()) содержит строки вида {ОбщийМодуль.Имя.Модуль(15)} — имя модуля и номер строки на каждом уровне. По ним видно, в каком модуле родилось исключение, даже если поймали его на клиенте.

&НаКлиенте
Процедура ЗапуститьОбработку(Команда)
	Попытка
		ОбработатьНаСервере();
	Исключение
		Текст = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
		ЗаписатьОшибкуВЖурнал(Текст);
		ПоказатьПредупреждение(, КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
	КонецПопытки;
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ЗаписатьОшибкуВЖурнал(Текст)
	ЗаписьЖурналаРегистрации("Обработка.Ошибка", УровеньЖурналаРегистрации.Ошибка, , , Текст);
КонецПроцедуры

Пользователю показывайте краткое представление, а подробное пишите в журнал регистрации: там могут быть имена модулей и фрагменты кода.

Фоновые задания вместо потоков

Потоков в языке 1С нет. Параллельная работа делается фоновыми заданиями: ФоновыеЗадания.Выполнить("ОбщийМодуль.Процедура", Параметры). Процедура должна быть экспортной в серверном общем модуле.

Что важно знать про контекст фонового задания:

  • 🗄 код выполняется только на сервере, клиентского контекста нет;
  • 🚫 нельзя открыть форму, вызвать диалог или клиентский метод;
  • 💬 Сообщить() и СообщениеПользователю не появятся на экране — сообщения копятся, их забирают методом ПолучитьСообщенияПользователю() у объекта фонового задания;
  • 👤 задание работает от имени пользователя, который его запустил, и с его правами;
  • 🔁 параметры передаются копией, общих переменных с вызвавшим сеансом нет.

Как внутри серверного кода понять, что он работает в фоне:

Функция ВыполняетсяВФоне() Экспорт
	ТекущееЗадание = ПолучитьТекущийСеансИнформационнойБазы().ПолучитьФоновоеЗадание();
	Возврат ТекущееЗадание <> Неопределено;
КонецФункции

Если сеанс не фоновый, метод вернёт Неопределено. Этой проверкой удобно отключать то, что в фоне бессмысленно: запись настроек формы, тяжёлое логирование для интерактива, вопросы пользователю.

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

Сравнение способов проверки

СпособЧто показываетГде работаетКогда брать
#Если Сервер / #Если Клиентконтекст компиляции строкивездеобщий модуль «клиент-сервер», разная логика ветвей
#Если ВебКлиент, ТонкийКлиентвид клиентаклиентобход ограничений веб-клиента
Директивы &НаКлиенте / &НаСерверезадают, а не проверяютмодули форм и командпроектирование формы
ИмяПриложения сеансакто запустил сеанссерверотличить фон, COM, HTTP-сервис от пользователя
ПолучитьФоновоеЗадание()фоновое ли это заданиесерверотключить интерактив в фоне
СистемнаяИнформацияОС, версия, браузерклиент и сервердиагностика окружения
Отладчик, стек вызововцепочку вызововконфигуратор / EDTразбор конкретной ошибки

Проверка через типы: почему это ловушка

В старом коде встречается приём: попробовать создать серверный тип и поймать исключение. Например, вызвать Тип("СправочникОбъект.Номенклатура") внутри Попытка и решить, что раз ошибки нет — мы на сервере.

Работает это ненадёжно и медленно:

  • 🐢 исключение — дорогая операция, а такие проверки часто стоят в цикле;
  • 🧩 результат зависит от того, есть ли объект в конфигурации, а не только от контекста;
  • 🕳 настоящая ошибка внутри Попытка маскируется под «мы на клиенте».

Для данных, а не для контекста, проверка типа уместна. На клиенте форма отдаёт ДанныеФормыСтруктура или ДанныеФормыКоллекция. Если общая процедура принимает и объект, и данные формы, проверка ТипЗнч(Параметр) = Тип("ДанныеФормыСтруктура") честно скажет, что пришло. А контекст выполнения определяйте препроцессором.

Ошибки при переходе с клиента на сервер

Самая частая — вызов метода, который в этом контексте не скомпилирован. Платформа сообщает Процедура или функция с указанным именем не определена, хотя метод в модуле есть. Причина обычно одна из трёх:

  1. У общего модуля не включён флажок «Вызов сервера», а его метод вызывают с клиента.
  2. Метод формы помечен &НаСервере, а вызывают его из клиентского модуля команды или из другого общего модуля.
  3. Код обёрнут в #Если Сервер, и на клиенте этой ветки просто нет.

Вторая группа — передача значений. Между клиентом и сервером ходят только сериализуемые значения: примитивы, ссылки, структуры, массивы, таблицы через данные формы. Попытка передать ДокументОбъект, Запрос, ТабличныйДокумент из серверной функции без контекста или COM-объект с клиента даёт ошибку при вызове. Решение — передавать ссылку или структуру с нужными полями.

Третья — исключения, брошенные на сервере. ВызватьИсключение в серверной процедуре дойдёт до клиента, и его можно поймать в клиентском Попытка … Исключение. Но если исключение произошло внутри транзакции и вы перехватили его на сервере, транзакцию нужно отменить там же. Не продолжайте запись в транзакции после пойманного исключения — платформа всё равно не даст её зафиксировать, а данные останутся в непредсказуемом состоянии.

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

Диагностика: выберите симптом

Веб-клиент: что проверять отдельно

Веб-клиент — это клиент на JavaScript в браузере. Символ Клиент в нём истинен, но возможностей меньше, чем у тонкого клиента. Поэтому для него нужна своя ветка #Если ВебКлиент.

Что обычно ломается в браузере:

  • 🔌 Новый COMОбъект(...) — COM недоступен;
  • 📝 Выполнить() и Вычислить() — в веб-клиенте их нет;
  • 📁 прямая работа с файлами на диске — нужны асинхронные методы и расширение для работы с файлами;
  • 🪟 модальные окна — браузер их блокирует, используйте асинхронные вызовы ПоказатьВопрос, ПоказатьПредупреждение или Ждать в процедурах Асинх на свежих версиях платформы.

Пример безопасной ветки:

&НаКлиенте
Процедура ОткрытьExcel(Команда)
	#Если ВебКлиент Тогда
		ПоказатьПредупреждение(, "Выгрузка в Excel через COM недоступна в браузере. Сохраните файл и откройте его вручную.");
	#Иначе
		Excel = Новый COMОбъект("Excel.Application");
		Excel.Visible = Истина;
	#КонецЕсли
КонецПроцедуры

Если код на сервере должен знать, что пользователь сидит в браузере, проверьте там ИмяПриложения сеанса: для веб-клиента это WebClient.

В обычном приложении (толстый клиент) деление жёстче не задано: модули формы выполняются на клиенте, а серверные общие модули вызываются напрямую. Для такого кода отдельная ветка — #Если ТолстыйКлиентОбычноеПриложение.

Частые вопросы

Почему #Если Сервер не срабатывает в модуле формы?
Процедура с директивой &НаКлиенте целиком компилируется только для клиента, ветка Сервер внутри неё пуста. Инструкции препроцессора полезны в общих модулях с флажками и клиента, и сервера.
Можно ли из клиента узнать, что база файловая?
Да. Строка соединения СтрокаСоединенияИнформационнойБазы() для файловой базы начинается с File=. Её можно разобрать и на клиенте, и на сервере.
Чем ИмяПриложения отличается от #Если ТонкийКлиент?
Препроцессор работает на клиенте и знает только вид клиента. ИмяПриложения читается на сервере и показывает тип сеанса, включая фоновые задания и внешние соединения.
Влияет ли проверка препроцессором на скорость?
Нет. Ветки отбрасываются при компиляции модуля, во время выполнения проверки уже нет.
Как запретить выполнение процедуры из фонового задания?
В начале процедуры проверьте ПолучитьТекущийСеансИнформационнойБазы().ПолучитьФоновоеЗадание(). Если результат не Неопределено — выйдите или вызовите исключение с понятным текстом.