Контекст выполнения в 1С 8.3: инструкции препроцессора, сеанс и фоновые задания
Содержание
Частое заблуждение — искать в 1С встроенную функцию вроде ЭтоСервер(). Такой функции в платформе нет. Проверить, на клиенте или на сервере выполняется код, можно инструкцией препроцессора #Если Сервер Тогда … #КонецЕсли, а тип сеанса (тонкий клиент, фоновое задание, COM-соединение) — через ПолучитьТекущийСеансИнформационнойБазы().ИмяПриложения.
Первое отвечает на вопрос «в каком контексте скомпилирована эта строка». Второе — «кто вообще запустил сеанс». Это разные вещи, и путаница между ними даёт большинство ошибок с объектами на клиенте. Всё ниже относится к платформе 8.3 и управляемому приложению; отличия обычного приложения отмечены отдельно.
Почему в 1С нет «ЭтоСервер» и как его заменить
В управляемом приложении один и тот же модуль может компилироваться дважды: отдельно для клиента и отдельно для сервера. Поэтому вопрос «где я?» решается не в момент выполнения, а в момент компиляции. Для этого служат символы препроцессора.
Основные символы, которые понимает #Если:
- 🖥
Клиент/НаКлиенте— любой клиентский контекст; - 🗄
Сервер/НаСервере— серверный контекст; - 🪶
ТонкийКлиент— тонкий клиент; - 🌐
ВебКлиент— веб-клиент в браузере; - 🧱
ТолстыйКлиентУправляемоеПриложениеиТолстыйКлиентОбычноеПриложение— толстый клиент в двух режимах; - 🔌
ВнешнееСоединение— COM-соединение; - 📱
МобильноеПриложениеКлиент,МобильноеПриложениеСервер— мобильная платформа.
Символы можно комбинировать через И, ИЛИ, НЕ. Если нужна привычная функция, сделайте её сами в общем модуле, у которого включены флажки «Клиент (управляемое приложение)» и «Сервер»:
// Общий модуль ОбщегоНазначенияКлиентСерверСвой
Функция ЭтоСерверныйКонтекст() Экспорт
#Если Сервер Тогда
Возврат Истина;
#Иначе
Возврат Ложь;
#КонецЕсли
КонецФункции
Функция ЭтоВебКлиент() Экспорт
#Если ВебКлиент Тогда
Возврат Истина;
#Иначе
Возврат Ложь;
#КонецЕсли
КонецФункции
Модуль компилируется в обоих контекстах, и в каждом остаётся своя ветка. На клиенте функция вернёт Ложь, в серверной процедуре — Истина.
В файловом варианте серверный код физически выполняется в том же процессе, что и клиент. Но символ Сервер всё равно истинен только в серверном контексте. Проверка препроцессором ведёт себя одинаково в файловой и клиент-серверной базе — на это и стоит опираться.
Директивы компиляции: где окажется объект
Второй уровень — директивы перед процедурами в модулях форм и команд. Они не проверяют контекст, а задают его.
| Директива | Где выполняется | Доступ к данным формы |
|---|---|---|
&НаКлиенте | клиент | да, клиентские данные формы |
&НаСервере | сервер | да, форма передаётся на сервер |
&НаСервереБезКонтекста | сервер | нет, только параметры |
&НаКлиентеНаСервереБезКонтекста | там, откуда вызвали | нет |
Отсюда прямой ответ на вопрос из заголовка про объект. Прикладной объект — СправочникОбъект, ДокументОбъект и подобные — живёт только на сервере. На клиенте в форме вместо него лежат ДанныеФормыСтруктура. Чтобы получить настоящий объект, нужна серверная процедура и РеквизитФормыВЗначение("Объект"), а для записи обратно — ЗначениеВРеквизитФормы.
Ссылки (СправочникСсылка, ДокументСсылка) доступны и на клиенте, и на сервере. Но прочитать через точку реквизит ссылки на тонком клиенте нельзя: за данными нужно сходить на сервер.
&НаКлиенте
Процедура ПроверитьКонтрагента(Команда)
Если ИННЗаполнен(Объект.Контрагент) Тогда
Сообщить("ИНН есть");
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ИННЗаполнен(Контрагент)
Возврат ЗначениеЗаполнено(Контрагент.ИНН);
КонецФункции
В общих модулях ту же роль играют флажки свойств: «Сервер», «Клиент (управляемое приложение)», «Внешнее соединение», «Вызов сервера». Модуль только с флажком «Сервер» и без «Вызов сервера» с клиента напрямую не вызвать.
Кто запустил сеанс: ИмяПриложения и СистемнаяИнформация
Препроцессор знает контекст строки кода, но не знает, что в серверном коде сейчас работает фоновое задание или внешнее соединение. Для этого есть свойство сеанса.
// Только серверный контекст
Функция ТипСеанса() Экспорт
Сеанс = ПолучитьТекущийСеансИнформационнойБазы();
Возврат Сеанс.ИмяПриложения;
КонецФункции
Типичные значения, которые вернёт ИмяПриложения:
| Значение | Кто подключился |
|---|---|
1CV8C | тонкий клиент |
1CV8 | толстый клиент |
WebClient | веб-клиент |
Designer | конфигуратор |
COMConnection | внешнее соединение |
BackgroundJob | фоновое задание |
WSConnection | веб-сервис |
HTTPServiceConnection | HTTP-сервис |
ПолучитьТекущийСеансИнформационнойБазы() доступна только на сервере. С клиента её зовут через серверную функцию без контекста.
На клиенте полезен объект СистемнаяИнформация. Свойство ТипПлатформы показывает операционную систему и разрядность, ВерсияПриложения — номер платформы, ИнформацияПрограммыПросмотра — строку браузера в веб-клиенте.
&НаКлиенте
Процедура ПоказатьОкружение(Команда)
Инфо = Новый СистемнаяИнформация;
Сообщить(СтрШаблон("Платформа %1, ОС %2", Инфо.ВерсияПриложения, Инфо.ТипПлатформы));
КонецПроцедуры
Стек вызовов: как понять, откуда пришёл вызов
Встроенной функции, которая вернула бы стек вызовов прямо в коде, в языке нет. Поэтому «на клиенте или на сервере упало» смотрят двумя способами.
Отладчик. При остановке на точке останова окно «Стек вызовов» показывает цепочку процедур. В нём видно, где произошёл переход с клиента на сервер: клиентские процедуры формы и серверные идут разными предметами отладки. Чтобы отладчик остановился в серверном коде клиент-серверной базы, сервер 1С должен быть запущен с разрешённой отладкой, а в конфигураторе в настройках подключения отладки нужно отметить серверные предметы и фоновые задания.
Подробное представление ошибки. Текст из ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()) содержит строки вида {ОбщийМодуль.Имя.Модуль(15)} — имя модуля и номер строки на каждом уровне. По ним видно, в каком модуле родилось исключение, даже если поймали его на клиенте.
&НаКлиенте
Процедура ЗапуститьОбработку(Команда)
Попытка
ОбработатьНаСервере();
Исключение
Текст = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
ЗаписатьОшибкуВЖурнал(Текст);
ПоказатьПредупреждение(, КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ЗаписатьОшибкуВЖурнал(Текст)
ЗаписьЖурналаРегистрации("Обработка.Ошибка", УровеньЖурналаРегистрации.Ошибка, , , Текст);
КонецПроцедуры
Пользователю показывайте краткое представление, а подробное пишите в журнал регистрации: там могут быть имена модулей и фрагменты кода.
Фоновые задания вместо потоков
Потоков в языке 1С нет. Параллельная работа делается фоновыми заданиями: ФоновыеЗадания.Выполнить("ОбщийМодуль.Процедура", Параметры). Процедура должна быть экспортной в серверном общем модуле.
Что важно знать про контекст фонового задания:
- 🗄 код выполняется только на сервере, клиентского контекста нет;
- 🚫 нельзя открыть форму, вызвать диалог или клиентский метод;
- 💬
Сообщить()иСообщениеПользователюне появятся на экране — сообщения копятся, их забирают методомПолучитьСообщенияПользователю()у объекта фонового задания; - 👤 задание работает от имени пользователя, который его запустил, и с его правами;
- 🔁 параметры передаются копией, общих переменных с вызвавшим сеансом нет.
Как внутри серверного кода понять, что он работает в фоне:
Функция ВыполняетсяВФоне() Экспорт
ТекущееЗадание = ПолучитьТекущийСеансИнформационнойБазы().ПолучитьФоновоеЗадание();
Возврат ТекущееЗадание <> Неопределено;
КонецФункции
Если сеанс не фоновый, метод вернёт Неопределено. Этой проверкой удобно отключать то, что в фоне бессмысленно: запись настроек формы, тяжёлое логирование для интерактива, вопросы пользователю.
В файловой базе фоновые задания тоже работают, но выполняются внутри клиентского процесса. Если закрыть программу, задание прервётся.
Сравнение способов проверки
| Способ | Что показывает | Где работает | Когда брать |
|---|---|---|---|
#Если Сервер / #Если Клиент | контекст компиляции строки | везде | общий модуль «клиент-сервер», разная логика ветвей |
#Если ВебКлиент, ТонкийКлиент | вид клиента | клиент | обход ограничений веб-клиента |
Директивы &НаКлиенте / &НаСервере | задают, а не проверяют | модули форм и команд | проектирование формы |
ИмяПриложения сеанса | кто запустил сеанс | сервер | отличить фон, COM, HTTP-сервис от пользователя |
ПолучитьФоновоеЗадание() | фоновое ли это задание | сервер | отключить интерактив в фоне |
СистемнаяИнформация | ОС, версия, браузер | клиент и сервер | диагностика окружения |
| Отладчик, стек вызовов | цепочку вызовов | конфигуратор / EDT | разбор конкретной ошибки |
Проверка через типы: почему это ловушка
В старом коде встречается приём: попробовать создать серверный тип и поймать исключение. Например, вызвать Тип("СправочникОбъект.Номенклатура") внутри Попытка и решить, что раз ошибки нет — мы на сервере.
Работает это ненадёжно и медленно:
- 🐢 исключение — дорогая операция, а такие проверки часто стоят в цикле;
- 🧩 результат зависит от того, есть ли объект в конфигурации, а не только от контекста;
- 🕳 настоящая ошибка внутри
Попыткамаскируется под «мы на клиенте».
Для данных, а не для контекста, проверка типа уместна. На клиенте форма отдаёт ДанныеФормыСтруктура или ДанныеФормыКоллекция. Если общая процедура принимает и объект, и данные формы, проверка ТипЗнч(Параметр) = Тип("ДанныеФормыСтруктура") честно скажет, что пришло. А контекст выполнения определяйте препроцессором.
Ошибки при переходе с клиента на сервер
Самая частая — вызов метода, который в этом контексте не скомпилирован. Платформа сообщает Процедура или функция с указанным именем не определена, хотя метод в модуле есть. Причина обычно одна из трёх:
- У общего модуля не включён флажок «Вызов сервера», а его метод вызывают с клиента.
- Метод формы помечен
&НаСервере, а вызывают его из клиентского модуля команды или из другого общего модуля. - Код обёрнут в
#Если Сервер, и на клиенте этой ветки просто нет.
Вторая группа — передача значений. Между клиентом и сервером ходят только сериализуемые значения: примитивы, ссылки, структуры, массивы, таблицы через данные формы. Попытка передать ДокументОбъект, Запрос, ТабличныйДокумент из серверной функции без контекста или COM-объект с клиента даёт ошибку при вызове. Решение — передавать ссылку или структуру с нужными полями.
Третья — исключения, брошенные на сервере. ВызватьИсключение в серверной процедуре дойдёт до клиента, и его можно поймать в клиентском Попытка … Исключение. Но если исключение произошло внутри транзакции и вы перехватили его на сервере, транзакцию нужно отменить там же. Не продолжайте запись в транзакции после пойманного исключения — платформа всё равно не даст её зафиксировать, а данные останутся в непредсказуемом состоянии.
В фоновом задании исключение не всплывает к пользователю. Задание переходит в состояние «Завершено аварийно», а текст ошибки лежит в свойстве ИнформацияОбОшибке объекта фонового задания.
Веб-клиент: что проверять отдельно
Веб-клиент — это клиент на JavaScript в браузере. Символ Клиент в нём истинен, но возможностей меньше, чем у тонкого клиента. Поэтому для него нужна своя ветка #Если ВебКлиент.
Что обычно ломается в браузере:
- 🔌
Новый COMОбъект(...)— COM недоступен; - 📝
Выполнить()иВычислить()— в веб-клиенте их нет; - 📁 прямая работа с файлами на диске — нужны асинхронные методы и расширение для работы с файлами;
- 🪟 модальные окна — браузер их блокирует, используйте асинхронные вызовы
ПоказатьВопрос,ПоказатьПредупреждениеилиЖдатьв процедурахАсинхна свежих версиях платформы.
Пример безопасной ветки:
&НаКлиенте
Процедура ОткрытьExcel(Команда)
#Если ВебКлиент Тогда
ПоказатьПредупреждение(, "Выгрузка в Excel через COM недоступна в браузере. Сохраните файл и откройте его вручную.");
#Иначе
Excel = Новый COMОбъект("Excel.Application");
Excel.Visible = Истина;
#КонецЕсли
КонецПроцедуры
Если код на сервере должен знать, что пользователь сидит в браузере, проверьте там ИмяПриложения сеанса: для веб-клиента это WebClient.
В обычном приложении (толстый клиент) деление жёстче не задано: модули формы выполняются на клиенте, а серверные общие модули вызываются напрямую. Для такого кода отдельная ветка — #Если ТолстыйКлиентОбычноеПриложение.
Частые вопросы
Почему #Если Сервер не срабатывает в модуле формы?
&НаКлиенте целиком компилируется только для клиента, ветка Сервер внутри неё пуста. Инструкции препроцессора полезны в общих модулях с флажками и клиента, и сервера.Можно ли из клиента узнать, что база файловая?
СтрокаСоединенияИнформационнойБазы() для файловой базы начинается с File=. Её можно разобрать и на клиенте, и на сервере.Чем ИмяПриложения отличается от #Если ТонкийКлиент?
ИмяПриложения читается на сервере и показывает тип сеанса, включая фоновые задания и внешние соединения.Влияет ли проверка препроцессором на скорость?
Как запретить выполнение процедуры из фонового задания?
ПолучитьТекущийСеансИнформационнойБазы().ПолучитьФоновоеЗадание(). Если результат не Неопределено — выйдите или вызовите исключение с понятным текстом.