Остановка кода в 1С 8.3: Возврат, исключения, Отказ и фоновые задания
Содержание
Чтобы программно прервать выполнение в 1С, выберите инструмент по масштабу остановки. Возврат выходит из текущей процедуры или функции. Прервать выходит только из цикла. ВызватьИсключение обрывает всю цепочку вызовов до ближайшего блока Попытка. Отказ = Истина в обработчике события отменяет запись, проведение или открытие формы.
Ошибка в выборе стоит дорого. Поставили Прервать вместо Возврат, и код после цикла всё равно выполнился. Вызвали исключение внутри транзакции и не отменили её, и следующая запись упала с В данной транзакции уже происходили ошибки!. Ниже собраны все способы остановки кода в 1С:Предприятии 8.3 с примерами и подводными камнями. Материал общий для платформы и не зависит от конфигурации.
Операторы прерывания: Возврат, Прервать, Продолжить
Три оператора встроенного языка управляют ходом выполнения внутри одного модуля. Они не порождают ошибок и не видны пользователю.
| Оператор | Что останавливает | Где работает | Что выполнится дальше |
|---|---|---|---|
Возврат | Текущую процедуру или функцию | В любом месте процедуры | Код вызывающей процедуры после строки вызова |
Возврат Значение | Функцию с результатом | Только в функции | То же, вызывающий код получает значение |
Прервать | Ближайший цикл | Только внутри Для, Для каждого, Пока | Первая строка после КонецЦикла |
Продолжить | Текущую итерацию цикла | Только внутри цикла | Следующая итерация |
Пример: ищем первый документ с пустым контрагентом и выходим из функции сразу, без лишнего перебора.
Функция ПервыйДокументБезКонтрагента(МассивДокументов)
Для Каждого Документ Из МассивДокументов Цикл
Если Не ЗначениеЗаполнено(Документ.Контрагент) Тогда
Возврат Документ; // выход из функции, цикл дальше не идёт
КонецЕсли;
КонецЦикла;
Возврат Неопределено;
КонецФункции
Что учесть:
- 🔹
Прерватьво вложенном цикле выходит только из внутреннего. Чтобы выйти из обоих, заведите флаг и проверяйте его во внешнем цикле, либо вынесите циклы в отдельную функцию и используйтеВозврат. - 🔹 В функции каждая ветка должна заканчиваться
Возврат Значение. Иначе функция вернётНеопределено, и ошибка проявится далеко от места причины. - 🔹 Операторов
gotoв привычном виде нет.Перейти ~Меткаформально существует, но в типовых конфигурациях его не используют: код с ним трудно читать и отлаживать.
Ранний выход через Возврат в начале процедуры («проверили условие, не подходит, вышли») читается лучше, чем три уровня вложенных Если.
Остановка через исключение: ВызватьИсключение и Попытка
Когда нужно остановить не одну процедуру, а всю операцию целиком, используйте исключение. ВызватьИсключение прерывает текущую процедуру и все вызывающие её, пока не встретится блок Попытка ... Исключение. Если такого блока нет, пользователь увидит окно ошибки с вашим текстом.
Процедура ЗагрузитьПрайс(ПутьКФайлу) Экспорт
Файл = Новый Файл(ПутьКФайлу);
Если Не Файл.Существует() Тогда
ВызватьИсключение СтрШаблон("Файл прайса не найден: %1", ПутьКФайлу);
КонецЕсли;
// дальше разбор файла
КонецПроцедуры
Перехват и запись причины в журнал регистрации:
Попытка
ЗагрузитьПрайс(ПутьКФайлу);
Исключение
Инфо = ИнформацияОбОшибке();
ЗаписьЖурналаРегистрации("Загрузка прайса",
УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(Инфо));
Сообщить(КраткоеПредставлениеОшибки(Инфо));
КонецПопытки;
Правила, которые экономят часы отладки:
- Не глушите исключение пустым блоком
Исключение КонецПопытки. Операция «успешно» не выполнится, а следов не останется. - Если перехватили ошибку только чтобы записать её в журнал, пробросьте её дальше: внутри блока
ИсключениеоператорВызватьИсключение;без параметров повторяет исходную ошибку. - Для пользователя берите
КраткоеПредставлениеОшибки(), для журналаПодробноеПредставлениеОшибки(): там есть модуль и номер строки. - Исключение, вызванное на сервере, долетает до клиента. Пользователь увидит текст, поэтому пишите его человеческим языком.
Исключение не заменяет проверку данных. Для «не заполнен склад» правильнее сообщение пользователю и Отказ, а исключение оставьте для ситуаций, когда продолжать работу нельзя.
Отказ в обработчиках событий: отмена записи, проведения, открытия
Многие события объектов и форм имеют параметр Отказ. Присвоили ему Истина, и платформа отменяет действие, ради которого событие вызвано. Код процедуры при этом не останавливается сам, поэтому после установки флага обычно пишут Возврат.
| Обработчик | Что отменяет Отказ = Истина |
|---|---|
ПередЗаписью (модуль объекта) | Запись объекта, транзакция откатывается |
ОбработкаПроведения | Проведение документа |
ОбработкаПроверкиЗаполнения | Запись или проведение после проверки |
ПередУдалением | Непосредственное удаление |
ПриСозданииНаСервере (форма) | Открытие формы |
ПриОткрытии (форма) | Открытие формы |
ПередЗакрытием (форма) | Закрытие формы |
Пример: запрещаем проводить реализацию без договора.
Процедура ОбработкаПроверкиЗаполнения(Отказ, ПроверяемыеРеквизиты)
Если Не ЗначениеЗаполнено(ДоговорКонтрагента) Тогда
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Укажите договор с контрагентом.";
Сообщение.Поле = "ДоговорКонтрагента";
Сообщение.УстановитьДанные(ЭтотОбъект);
Сообщение.Сообщить();
Отказ = Истина;
Возврат;
КонецЕсли;
КонецПроцедуры
Чем Отказ лучше исключения здесь: платформа собирает все сообщения сразу, пользователь видит их списком и кликом переходит к нужному полю. Исключение показало бы одну ошибку и оборвало остальные проверки.
Прерывание длительных процессов и фоновых заданий
Цикл на клиенте: Ctrl+Break
Пользователь может прервать клиентский цикл сочетанием Ctrl+Break, если внутри цикла вызывается ОбработкаПрерыванияПользователя(). Метод проверяет, нажато ли сочетание, и при нажатии предлагает остановить выполнение.
&НаКлиенте
Процедура ОбойтиФайлы(Команда)
Для Каждого ИмяФайла Из СписокФайлов Цикл
ОбработкаПрерыванияПользователя();
ОбработатьФайл(ИмяФайла.Значение);
КонецЦикла;
КонецПроцедуры
Метод работает только в клиентском контексте. Серверный цикл так не остановить: пока сервер не вернул управление, клиент ждёт.
Фоновое задание: запуск и отмена
Долгие операции на сервере выносят в фоновое задание. Тогда интерфейс не зависает, а задание можно отменить из кода.
&НаСервереБезКонтекста
Функция ЗапуститьПересчет()
Параметры = Новый Массив;
Параметры.Добавить(ТекущаяДатаСеанса());
Задание = ФоновыеЗадания.Выполнить("ОбщийМодульСервер.ПересчитатьОстатки",
Параметры, , "Пересчёт остатков");
Возврат Задание.УникальныйИдентификатор;
КонецФункции
&НаСервереБезКонтекста
Процедура ОтменитьПересчет(Идентификатор)
Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(Идентификатор);
Если Задание <> Неопределено
И Задание.Состояние = СостояниеФоновогоЗадания.Активно Тогда
Задание.Отменить();
КонецЕсли;
КонецПроцедуры
Состояния задания и что с ними делать:
СостояниеФоновогоЗадания | Что значит | Что смотреть |
|---|---|---|
Активно | Выполняется | Можно вызвать Отменить() |
Завершено | Отработало без ошибок | Результат, сообщения через ПолучитьСообщенияПользователю() |
ЗавершеноАварийно | Упало с исключением | Свойство ИнформацияОбОшибке |
Отменено | Отменено из кода или администратором | Незафиксированная транзакция откатилась |
Отменить() срабатывает не мгновенно. Если задание в этот момент ждёт тяжёлый запрос к СУБД, оно остановится после того, как запрос вернёт управление платформе.
Мягкая остановка по флагу
Для заданий на десятки минут надёжнее кооперативная остановка. Задание само периодически проверяет признак «пора остановиться», например запись в регистре сведений, и аккуратно выходит через Возврат после очередной порции. Плюс подхода: вы сами решаете, где граница порции, и не оставляете данные наполовину обработанными.
В конфигурациях на БСП для этого есть модуль ДлительныеОперации: задание запускают через ДлительныеОперации.ВыполнитьВФоне, а отменяют через ДлительныеОперации.ОтменитьВыполнениеЗадания. Сам механизм тот же, что выше.
Возврат (или Прервать, если выходите только из цикла).ВызватьИсключение с понятным текстом, перехват в Попытка на верхнем уровне.Отказ = Истина в обработчике события и сообщите причину пользователю.ОбработкаПрерыванияПользователя() в теле цикла, пользователь остановит его по Ctrl+Break.Отменить() или по флагу, который задание проверяет само.Транзакции, блокировки и монопольный режим при прерывании
Прерывание внутри транзакции требует дисциплины. Если исключение произошло после НачатьТранзакцию(), транзакцию нужно явно отменить. Иначе любое следующее обращение к базе в этой транзакции завершится ошибкой В данной транзакции уже происходили ошибки!.
Шаблон, который используют в типовых конфигурациях:
НачатьТранзакцию();
Попытка
Блокировка = Новый БлокировкаДанных;
Элемент = Блокировка.Добавить("РегистрНакопления.ОстаткиТоваров");
Элемент.Режим = РежимБлокировкиДанных.Исключительный;
Элемент.УстановитьЗначение("Склад", Склад);
Блокировка.Заблокировать();
// чтение остатков и запись движений
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
Что здесь происходит при прерывании:
- 🔒
Блокировка.Заблокировать()ждёт, пока другие сеансы освободят данные. Если время ожидания истекло, возникает исключение с текстом про конфликт блокировок, и мы попадаем в блокИсключение. - ↩️
ОтменитьТранзакцию()откатывает все изменения и снимает управляемые блокировки. - 📣
ВызватьИсключение;передаёт исходную ошибку наверх, чтобы вызывающий код знал о сбое.
Не вызывайте ЗафиксироватьТранзакцию() в блоке Исключение и не пытайтесь «продолжить» транзакцию после ошибки СУБД. Данные окажутся в непредсказуемом состоянии.
Монопольный режим
УстановитьМонопольныйРежим(Истина) переводит базу в монопольный режим. Он установится, только если в базе нет других сеансов, иначе возникнет исключение. Поэтому вызов тоже заворачивают в Попытка, а проверить текущее состояние можно функцией МонопольныйРежим().
Попытка
УстановитьМонопольныйРежим(Истина);
Исключение
ВызватьИсключение "Не удалось установить монопольный режим: в базе работают другие пользователи.";
КонецПопытки;
Попытка
ВыполнитьРеструктуризациюДанных();
Исключение
УстановитьМонопольныйРежим(Ложь);
ВызватьИсключение;
КонецПопытки;
УстановитьМонопольныйРежим(Ложь);
Монопольный режим нужен редко: для операций, которые не должны пересекаться ни с чьей работой. Для обычной записи хватает управляемых блокировок.
Блокировка новых сеансов
Если перед обслуживанием нужно не пускать пользователей, используют объект БлокировкаСеансов и метод УстановитьБлокировкуСеансов(). В конфигурациях на БСП то же самое делают без кода: Администрирование → Обслуживание, команда блокировки работы пользователей.
Принудительное завершение чужих сеансов обрывает их работу: несохранённые документы пропадут, незафиксированные транзакции откатятся. Предупредите пользователей заранее и делайте это вне рабочего времени.
Прерывание выполнения в управляемых формах
В управляемых формах диалоги асинхронные. Вызов ПоказатьВопрос() не ждёт ответа: код после него выполняется сразу. Частая ошибка: разработчик ждёт, что процедура «встанет» на вопросе, а она уже дошла до конца.
Классический вариант с обработчиком оповещения:
&НаКлиенте
Процедура Очистить(Команда)
Оповещение = Новый ОписаниеОповещения("ОчиститьЗавершение", ЭтотОбъект);
ПоказатьВопрос(Оповещение, "Очистить табличную часть?", РежимДиалогаВопрос.ДаНет);
// здесь ничего не пишем: ответ придёт в ОчиститьЗавершение
КонецПроцедуры
&НаКлиенте
Процедура ОчиститьЗавершение(Ответ, ДополнительныеПараметры) Экспорт
Если Ответ <> КодВозвратаДиалога.Да Тогда
Возврат;
КонецЕсли;
Объект.Товары.Очистить();
КонецПроцедуры
На платформе 8.3.18 и выше тот же сценарий пишут линейно, через асинхронную процедуру и Ждать:
&НаКлиенте
Асинх Процедура Очистить(Команда)
Ответ = Ждать ВопросАсинх("Очистить табличную часть?", РежимДиалогаВопрос.ДаНет);
Если Ответ <> КодВозвратаДиалога.Да Тогда
Возврат;
КонецЕсли;
Объект.Товары.Очистить();
КонецПроцедуры
Ещё два случая, которые относятся к остановке в формах:
- ⏱ Обработчик ожидания, подключённый через
ПодключитьОбработчикОжидания("ОбновитьСтатус", 2), работает, пока его не отключат. Остановка:ОтключитьОбработчикОжидания("ОбновитьСтатус"), обычно в обработчике завершения фонового задания или вПриЗакрытии. - 🚪 В
ПередЗакрытиемпри закрытии всей программы параметрЗавершениеРаботыравенИстина. Показывать вопросы в этот момент нельзя: вместо диалога заполнитеТекстПредупрежденияи установитеОтказ = Истина, платформа покажет текст сама.
Завершить сеанс пользователя из клиентского кода можно методом ЗавершитьРаботуСистемы(). Первый параметр управляет вопросом о завершении, второй задаёт перезапуск программы.
Как найти, где и почему прервалась процедура
Если код остановился неожиданно, причину ищут в трёх местах.
1. Отладчик. В Конфигураторе или EDT включите остановку по ошибке: в Конфигураторе это Отладка → Остановка по ошибке. Отладчик встанет на строке, где возникло исключение, даже если оно потом перехвачено в Попытка. Так находят «проглоченные» ошибки. Точки останова ставят клавишей F9, пошаговое выполнение F11 (с заходом в процедуры) и F10 (без захода).
2. Журнал регистрации. Всё, что вы записали через ЗаписьЖурналаРегистрации, и системные ошибки сеансов видны в Администрирование → Обслуживание → Журнал регистрации (в БСП) или через Конфигуратор: Администрирование → Журнал регистрации. Отбор по уровню «Ошибка» и по событию быстро сужает поиск.
3. Свойства фонового задания. У аварийно завершённого задания смотрите ИнформацияОбОшибке, а сообщения, которые задание выводило через Сообщить, забирайте методом ПолучитьСообщенияПользователю().
ОтменитьТранзакцию() в блок Исключение и пробросьте ошибку дальше.Прервать, а нужен выход из процедуры. Замените на Возврат или проверяйте флаг после цикла.ПриЗаписи формы после записи) либо Отказ перезаписан ниже по коду. Перенесите проверку в ОбработкаПроверкиЗаполнения или ПередЗаписью и выходите сразу через Возврат.ПоказатьВопрос() асинхронный. Перенесите продолжение в обработчик оповещения или используйте Ждать ВопросАсинх().Для разбора частых сбоев на рабочем сервере включают технологический журнал с событием EXCP: он фиксирует исключения всех сеансов. Журнал быстро растёт, поэтому включайте его на время диагностики и с ограничением по объёму.
Частые вопросы
Можно ли прервать выполнение запроса к базе из кода 1С?
Чем ВызватьИсключение отличается от Отказ = Истина?
Отказ отменяет только действие события, позволяет собрать все сообщения и не прерывает остальные проверки.Выполнится ли код после Возврат в процедуре?
Возврат в той же ветке не выполняется.Как остановить регламентное задание, которое зависло?
ФоновыеЗадания.ПолучитьФоновыеЗадания() с отбором и вызовите Отменить(), либо завершите его сеанс в консоли кластера. Во втором случае оборвётся только этот сеанс.