Остановка кода в 1С 8.3: Возврат, исключения, Отказ и фоновые задания

· 10 мин

Содержание

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

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

Операторы прерывания: Возврат, Прервать, Продолжить

Три оператора встроенного языка управляют ходом выполнения внутри одного модуля. Они не порождают ошибок и не видны пользователю.

ОператорЧто останавливаетГде работаетЧто выполнится дальше
ВозвратТекущую процедуру или функциюВ любом месте процедурыКод вызывающей процедуры после строки вызова
Возврат ЗначениеФункцию с результатомТолько в функцииТо же, вызывающий код получает значение
ПрерватьБлижайший циклТолько внутри Для, Для каждого, ПокаПервая строка после КонецЦикла
ПродолжитьТекущую итерацию циклаТолько внутри циклаСледующая итерация

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

Функция ПервыйДокументБезКонтрагента(МассивДокументов)

    Для Каждого Документ Из МассивДокументов Цикл
        Если Не ЗначениеЗаполнено(Документ.Контрагент) Тогда
            Возврат Документ; // выход из функции, цикл дальше не идёт
        КонецЕсли;
    КонецЦикла;

    Возврат Неопределено;

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

Что учесть:

  • 🔹 Прервать во вложенном цикле выходит только из внутреннего. Чтобы выйти из обоих, заведите флаг и проверяйте его во внешнем цикле, либо вынесите циклы в отдельную функцию и используйте Возврат.
  • 🔹 В функции каждая ветка должна заканчиваться Возврат Значение. Иначе функция вернёт Неопределено, и ошибка проявится далеко от места причины.
  • 🔹 Операторов goto в привычном виде нет. Перейти ~Метка формально существует, но в типовых конфигурациях его не используют: код с ним трудно читать и отлаживать.

Ранний выход через Возврат в начале процедуры («проверили условие, не подходит, вышли») читается лучше, чем три уровня вложенных Если.

Остановка через исключение: ВызватьИсключение и Попытка

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

Процедура ЗагрузитьПрайс(ПутьКФайлу) Экспорт

    Файл = Новый Файл(ПутьКФайлу);
    Если Не Файл.Существует() Тогда
        ВызватьИсключение СтрШаблон("Файл прайса не найден: %1", ПутьКФайлу);
    КонецЕсли;

    // дальше разбор файла
КонецПроцедуры

Перехват и запись причины в журнал регистрации:

Попытка
    ЗагрузитьПрайс(ПутьКФайлу);
Исключение
    Инфо = ИнформацияОбОшибке();
    ЗаписьЖурналаРегистрации("Загрузка прайса",
        УровеньЖурналаРегистрации.Ошибка, , ,
        ПодробноеПредставлениеОшибки(Инфо));
    Сообщить(КраткоеПредставлениеОшибки(Инфо));
КонецПопытки;

Правила, которые экономят часы отладки:

  1. Не глушите исключение пустым блоком Исключение КонецПопытки. Операция «успешно» не выполнится, а следов не останется.
  2. Если перехватили ошибку только чтобы записать её в журнал, пробросьте её дальше: внутри блока Исключение оператор ВызватьИсключение; без параметров повторяет исходную ошибку.
  3. Для пользователя берите КраткоеПредставлениеОшибки(), для журнала ПодробноеПредставлениеОшибки(): там есть модуль и номер строки.
  4. Исключение, вызванное на сервере, долетает до клиента. Пользователь увидит текст, поэтому пишите его человеческим языком.

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

Отказ в обработчиках событий: отмена записи, проведения, открытия

Многие события объектов и форм имеют параметр Отказ. Присвоили ему Истина, и платформа отменяет действие, ради которого событие вызвано. Код процедуры при этом не останавливается сам, поэтому после установки флага обычно пишут Возврат.

ОбработчикЧто отменяет Отказ = Истина
ПередЗаписью (модуль объекта)Запись объекта, транзакция откатывается
ОбработкаПроведенияПроведение документа
ОбработкаПроверкиЗаполненияЗапись или проведение после проверки
ПередУдалениемНепосредственное удаление
ПриСозданииНаСервере (форма)Открытие формы
ПриОткрытии (форма)Открытие формы
ПередЗакрытием (форма)Закрытие формы

Пример: запрещаем проводить реализацию без договора.

Процедура ОбработкаПроверкиЗаполнения(Отказ, ПроверяемыеРеквизиты)

    Если Не ЗначениеЗаполнено(ДоговорКонтрагента) Тогда
        Сообщение = Новый СообщениеПользователю;
        Сообщение.Текст = "Укажите договор с контрагентом.";
        Сообщение.Поле = "ДоговорКонтрагента";
        Сообщение.УстановитьДанные(ЭтотОбъект);
        Сообщение.Сообщить();
        Отказ = Истина;
        Возврат;
    КонецЕсли;

КонецПроцедуры

Чем Отказ лучше исключения здесь: платформа собирает все сообщения сразу, пользователь видит их списком и кликом переходит к нужному полю. Исключение показало бы одну ошибку и оборвало остальные проверки.

Прерывание длительных процессов и фоновых заданий

Цикл на клиенте: Ctrl+Break

Пользователь может прервать клиентский цикл сочетанием Ctrl+Break, если внутри цикла вызывается ОбработкаПрерыванияПользователя(). Метод проверяет, нажато ли сочетание, и при нажатии предлагает остановить выполнение.

&НаКлиенте
Процедура ОбойтиФайлы(Команда)
    Для Каждого ИмяФайла Из СписокФайлов Цикл
        ОбработкаПрерыванияПользователя();
        ОбработатьФайл(ИмяФайла.Значение);
    КонецЦикла;
КонецПроцедуры

Метод работает только в клиентском контексте. Серверный цикл так не остановить: пока сервер не вернул управление, клиент ждёт.

Фоновое задание: запуск и отмена

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

&НаСервереБезКонтекста
Функция ЗапуститьПересчет()
    Параметры = Новый Массив;
    Параметры.Добавить(ТекущаяДатаСеанса());
    Задание = ФоновыеЗадания.Выполнить("ОбщийМодульСервер.ПересчитатьОстатки",
        Параметры, , "Пересчёт остатков");
    Возврат Задание.УникальныйИдентификатор;
КонецФункции

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

Состояния задания и что с ними делать:

СостояниеФоновогоЗаданияЧто значитЧто смотреть
АктивноВыполняетсяМожно вызвать Отменить()
ЗавершеноОтработало без ошибокРезультат, сообщения через ПолучитьСообщенияПользователю()
ЗавершеноАварийноУпало с исключениемСвойство ИнформацияОбОшибке
ОтмененоОтменено из кода или администраторомНезафиксированная транзакция откатилась

Отменить() срабатывает не мгновенно. Если задание в этот момент ждёт тяжёлый запрос к СУБД, оно остановится после того, как запрос вернёт управление платформе.

Мягкая остановка по флагу

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

В конфигурациях на БСП для этого есть модуль ДлительныеОперации: задание запускают через ДлительныеОперации.ВыполнитьВФоне, а отменяют через ДлительныеОперации.ОтменитьВыполнениеЗадания. Сам механизм тот же, что выше.

Что именно нужно остановить?

Транзакции, блокировки и монопольный режим при прерывании

Прерывание внутри транзакции требует дисциплины. Если исключение произошло после НачатьТранзакцию(), транзакцию нужно явно отменить. Иначе любое следующее обращение к базе в этой транзакции завершится ошибкой В данной транзакции уже происходили ошибки!.

Шаблон, который используют в типовых конфигурациях:

НачатьТранзакцию();
Попытка
    Блокировка = Новый БлокировкаДанных;
    Элемент = Блокировка.Добавить("РегистрНакопления.ОстаткиТоваров");
    Элемент.Режим = РежимБлокировкиДанных.Исключительный;
    Элемент.УстановитьЗначение("Склад", Склад);
    Блокировка.Заблокировать();

    // чтение остатков и запись движений

    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    ВызватьИсключение;
КонецПопытки;

Что здесь происходит при прерывании:

  • 🔒 Блокировка.Заблокировать() ждёт, пока другие сеансы освободят данные. Если время ожидания истекло, возникает исключение с текстом про конфликт блокировок, и мы попадаем в блок Исключение.
  • ↩️ ОтменитьТранзакцию() откатывает все изменения и снимает управляемые блокировки.
  • 📣 ВызватьИсключение; передаёт исходную ошибку наверх, чтобы вызывающий код знал о сбое.

Не вызывайте ЗафиксироватьТранзакцию() в блоке Исключение и не пытайтесь «продолжить» транзакцию после ошибки СУБД. Данные окажутся в непредсказуемом состоянии.

Монопольный режим

УстановитьМонопольныйРежим(Истина) переводит базу в монопольный режим. Он установится, только если в базе нет других сеансов, иначе возникнет исключение. Поэтому вызов тоже заворачивают в Попытка, а проверить текущее состояние можно функцией МонопольныйРежим().

Попытка
    УстановитьМонопольныйРежим(Истина);
Исключение
    ВызватьИсключение "Не удалось установить монопольный режим: в базе работают другие пользователи.";
КонецПопытки;

Попытка
    ВыполнитьРеструктуризациюДанных();
Исключение
    УстановитьМонопольныйРежим(Ложь);
    ВызватьИсключение;
КонецПопытки;
УстановитьМонопольныйРежим(Ложь);

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

Блокировка новых сеансов

Если перед обслуживанием нужно не пускать пользователей, используют объект БлокировкаСеансов и метод УстановитьБлокировкуСеансов(). В конфигурациях на БСП то же самое делают без кода: Администрирование → Обслуживание, команда блокировки работы пользователей.

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

Прерывание выполнения в управляемых формах

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

Классический вариант с обработчиком оповещения:

&НаКлиенте
Процедура Очистить(Команда)
    Оповещение = Новый ОписаниеОповещения("ОчиститьЗавершение", ЭтотОбъект);
    ПоказатьВопрос(Оповещение, "Очистить табличную часть?", РежимДиалогаВопрос.ДаНет);
    // здесь ничего не пишем: ответ придёт в ОчиститьЗавершение
КонецПроцедуры

&НаКлиенте
Процедура ОчиститьЗавершение(Ответ, ДополнительныеПараметры) Экспорт
    Если Ответ <> КодВозвратаДиалога.Да Тогда
        Возврат;
    КонецЕсли;
    Объект.Товары.Очистить();
КонецПроцедуры

На платформе 8.3.18 и выше тот же сценарий пишут линейно, через асинхронную процедуру и Ждать:

&НаКлиенте
Асинх Процедура Очистить(Команда)
    Ответ = Ждать ВопросАсинх("Очистить табличную часть?", РежимДиалогаВопрос.ДаНет);
    Если Ответ <> КодВозвратаДиалога.Да Тогда
        Возврат;
    КонецЕсли;
    Объект.Товары.Очистить();
КонецПроцедуры

Ещё два случая, которые относятся к остановке в формах:

  • ⏱ Обработчик ожидания, подключённый через ПодключитьОбработчикОжидания("ОбновитьСтатус", 2), работает, пока его не отключат. Остановка: ОтключитьОбработчикОжидания("ОбновитьСтатус"), обычно в обработчике завершения фонового задания или в ПриЗакрытии.
  • 🚪 В ПередЗакрытием при закрытии всей программы параметр ЗавершениеРаботы равен Истина. Показывать вопросы в этот момент нельзя: вместо диалога заполните ТекстПредупреждения и установите Отказ = Истина, платформа покажет текст сама.

Завершить сеанс пользователя из клиентского кода можно методом ЗавершитьРаботуСистемы(). Первый параметр управляет вопросом о завершении, второй задаёт перезапуск программы.

Как найти, где и почему прервалась процедура

Если код остановился неожиданно, причину ищут в трёх местах.

1. Отладчик. В Конфигураторе или EDT включите остановку по ошибке: в Конфигураторе это Отладка → Остановка по ошибке. Отладчик встанет на строке, где возникло исключение, даже если оно потом перехвачено в Попытка. Так находят «проглоченные» ошибки. Точки останова ставят клавишей F9, пошаговое выполнение F11 (с заходом в процедуры) и F10 (без захода).

2. Журнал регистрации. Всё, что вы записали через ЗаписьЖурналаРегистрации, и системные ошибки сеансов видны в Администрирование → Обслуживание → Журнал регистрации (в БСП) или через Конфигуратор: Администрирование → Журнал регистрации. Отбор по уровню «Ошибка» и по событию быстро сужает поиск.

3. Свойства фонового задания. У аварийно завершённого задания смотрите ИнформацияОбОшибке, а сообщения, которые задание выводило через Сообщить, забирайте методом ПолучитьСообщенияПользователю().

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

Для разбора частых сбоев на рабочем сервере включают технологический журнал с событием EXCP: он фиксирует исключения всех сеансов. Журнал быстро растёт, поэтому включайте его на время диагностики и с ограничением по объёму.

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

Можно ли прервать выполнение запроса к базе из кода 1С?
Метода «отменить запрос» во встроенном языке нет. Тяжёлый запрос выносят в фоновое задание: его можно отменить, а запрос к СУБД завершится вместе с ним.
Чем ВызватьИсключение отличается от Отказ = Истина?
Исключение обрывает всю цепочку вызовов и показывает одну ошибку. Отказ отменяет только действие события, позволяет собрать все сообщения и не прерывает остальные проверки.
Выполнится ли код после Возврат в процедуре?
Нет. Управление сразу уходит в вызывающую процедуру, на строку после вызова. Код ниже Возврат в той же ветке не выполняется.
Как остановить регламентное задание, которое зависло?
Регламентное задание выполняется как фоновое. Найдите его через ФоновыеЗадания.ПолучитьФоновыеЗадания() с отбором и вызовите Отменить(), либо завершите его сеанс в консоли кластера. Во втором случае оборвётся только этот сеанс.
Откатятся ли изменения, если пользователь закрыл программу во время обработки?
Незафиксированная транзакция откатится. Всё, что было записано и зафиксировано до этого, останется. Поэтому длинные обработки делят на порции с отдельными транзакциями.