Остановка кода во встроенном языке 1С 8.3: от выхода из процедуры до отмены фонового задания

· 10 мин

Содержание

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

Частое заблуждение — что Прервать останавливает процедуру. Нет: он завершает только ближайший цикл, а код после КонецЦикла выполнится как обычно. Ещё одна путаница — с долгими серверными вызовами: их не остановить ни оператором, ни кнопкой на клиенте. Ниже каждый случай со своим инструментом. Всё описанное относится к платформе 8.3 и работает в любой конфигурации — БП, ЗУП, УТ или собственной.

Выход из процедуры и функции оператором Возврат

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

Процедура ЗаполнитьТабличнуюЧасть(Документ)

    Если Не ЗначениеЗаполнено(Документ.Контрагент) Тогда
        Сообщить("Не указан контрагент — заполнение отменено");
        Возврат;
    КонецЕсли;

    // основная логика выполняется, только если контрагент указан

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

В функции Возврат обязан вернуть значение. Пустой Возврат; внутри функции синтаксическая проверка не пропустит.

Функция СуммаПоСтрокам(Таблица)

    Если Таблица.Количество() = 0 Тогда
        Возврат 0;
    КонецЕсли;

    Возврат Таблица.Итог("Сумма");

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

Что полезно помнить про Возврат:

  • 🔹 Он завершает только текущий метод. Если процедуру А вызвала процедура Б, то после Возврат в А продолжится Б.
  • 🔹 Внутри Попытка ... Исключение оператор работает так же: выход из метода происходит сразу, блок Исключение не выполняется.
  • 🔹 Охранные условия в начале метода читаются легче, чем глубокая вложенность Если ... Иначе на три уровня.

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

Прервать и Продолжить: как управлять циклами

Операторы Прервать и Продолжить действуют только внутри Для, Для Каждого и Пока.

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

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

НайденнаяСтрока = Неопределено;

Для Каждого Строка Из Товары Цикл

    Если Строка.Количество = 0 Тогда
        Продолжить; // пустые строки не проверяем
    КонецЕсли;

    Если Строка.Цена <= 0 Тогда
        НайденнаяСтрока = Строка;
        Прервать; // одна найденная строка — достаточно
    КонецЕсли;

КонецЦикла;

Если НайденнаяСтрока <> Неопределено Тогда
    Сообщить(СтрШаблон("Строка %1: не указана цена", НайденнаяСтрока.НомерСтроки));
КонецЕсли;

Выход из вложенных циклов

Во встроенном языке 1С нет оператора «прервать два цикла сразу» и нет меток. Прервать во внутреннем цикле вернёт вас во внешний. Рабочих приёмов три:

  1. Флаг. Во внутреннем цикле ставите Найдено = Истина и Прервать, во внешнем сразу проверяете флаг и тоже вызываете Прервать.
  2. Вынос в функцию. Оба цикла переносите в отдельную функцию и выходите из неё через Возврат — обычно это самый чистый вариант.
  3. Цикл Пока с условием. Условие выхода ставите в заголовок: Пока Не Найдено И Сч < Всего Цикл.

Не удаляйте элементы коллекции внутри Для Каждого по ней же: итератор собьётся и часть строк будет пропущена. Для удаления используйте обратный цикл: Для Сч = 1 По Коллекция.Количество() Цикл Элемент = Коллекция[Коллекция.Количество() - Сч];.

Исключения: как прервать всю цепочку вызовов

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

Процедура ПроверитьОстаток(Номенклатура, Количество) Экспорт

    Остаток = ПолучитьОстаток(Номенклатура);
    Если Остаток < Количество Тогда
        ВызватьИсключение СтрШаблон("Недостаточно остатка: нужно %1, есть %2",
            Формат(Количество, "ЧГ=0"), Формат(Остаток, "ЧГ=0"));
    КонецЕсли;

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

Перехват на верхнем уровне:

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

Правила, которые сберегут время:

  • 🧱 Не глушите исключения пустым блоком Исключение КонецПопытки. Ошибка исчезнет, а данные останутся в непонятном состоянии.
  • 🔁 Если блок Исключение только пишет в журнал, а решать проблему должен вызывающий код, допишите в конце ВызватьИсключение; без параметров — исходная ошибка пробросится дальше.
  • 🔒 В транзакции после исключения записывать уже нельзя: при следующем обращении к базе платформа выдаст В данной транзакции уже происходили ошибки. Транзакцию нужно отменить (ОтменитьТранзакцию()) и начинать заново.
  • 📌 Попытка ставьте вокруг конкретной рискованной операции — записи, обращения к файлу, HTTP-запроса, а не вокруг всей процедуры на 300 строк.

Параметр Отказ в обработчиках событий

В обработчиках ПередЗаписью, ОбработкаПроверкиЗаполнения, ОбработкаПроведения, ПередУдалением есть параметр Отказ. Это самый правильный способ остановить запись или проведение: Отказ = Истина; плюс сообщение пользователю. Платформа сама отменит транзакцию записи.

Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)

    Если Дата > ТекущаяДатаСеанса() + 86400 Тогда
        Сообщить("Дата документа больше завтрашней — запись отменена");
        Отказ = Истина;
        Возврат;
    КонецЕсли;

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

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

Остановка долгих процессов на клиенте и сервере

Где выполняется код, решает всё. Клиентский и серверный код прерываются разными средствами.

Ctrl+Break и ОбработкаПрерыванияПользователя

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

&НаКлиенте
Процедура ОбработатьФайлы(Команда)

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

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

Вызов дешёвый, но в цикле на миллион витков проверяйте нажатие, например, раз в 100 итераций.

Почему серверный вызов не остановить с клиента

Вызов &НаСервере из формы синхронный: клиент ждёт ответа, интерфейс замирает. Пока сервер не вернул управление, ни Ctrl+Break, ни закрытие формы выполнение не прервут. Если процедура считает 20 минут, пользователь будет смотреть на «песочные часы» все 20 минут.

Решение — перенести долгую работу в фоновое задание, а на клиенте опрашивать его состояние через ПодключитьОбработчикОжидания. Тогда форма остаётся живой, а пользователь получает кнопку «Отменить».

Где выполняетсяЧем прерватьОграничение
Клиент, циклCtrl+Break + ОбработкаПрерыванияПользователя()Работает только там, где есть вызов
Сервер, синхронный вызовНичем со стороны клиентаТолько завершение сеанса администратором
Фоновое заданиеФоновоеЗадание.Отменить()Код задания должен быть готов к остановке
Регламентное заданиеОтмена запущенного экземпляра, отключение расписанияСледующий запуск пройдёт по расписанию, если его не выключить

Отмена фонового задания из кода и вручную

Фоновое задание запускают методом ФоновыеЗадания.Выполнить(). Он возвращает объект задания, а его УникальныйИдентификатор сохраняют в реквизит формы, чтобы потом найти задание и отменить.

&НаСервере
Процедура ЗапуститьРасчетНаСервере()

    Параметры = Новый Массив;
    Параметры.Добавить(Объект.Ссылка);

    Задание = ФоновыеЗадания.Выполнить("РасчетыСервер.ДлительныйРасчет",
        Параметры, , "Пересчёт себестоимости");
    ИдентификаторЗадания = Задание.УникальныйИдентификатор;

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

&НаСервере
Процедура ОтменитьРасчетНаСервере()

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

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

После отмены задание переходит в состояние СостояниеФоновогоЗадания.Отменено. Остальные значения: Активно, Завершено, ЗавершеноАварийно — последнее означает, что внутри задания упало исключение; текст ошибки лежит в свойстве ИнформацияОбОшибке.

Аккуратная остановка изнутри задания

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

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

Отмена вручную администратором

В конфигурациях на БСП (БП 3.0, ЗУП 3.1, УТ 11) активные задания видны в разделе Администрирование → Обслуживание → Регламентные операции → Регламентные и фоновые задания. На вкладке фоновых заданий выберите активное и отмените его командой из командной панели.

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

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

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

Пользователь жалуется, что «всё висит», а код длинный. Прежде чем что-то прерывать, выясните, на какой строке он стоит.

Отладчик в Конфигураторе

  1. Запустите отладку из Конфигуратора или подключитесь к уже работающему сеансу через Отладка → Подключение.
  2. Когда процесс завис, выполните команду Отладка → Прервать. Выполнение остановится на текущей строке, и вы увидите место в модуле.
  3. Откройте стек вызовов и посмотрите, кто кого вызвал. Часто «висит» не цикл, а запрос внутри него.
  4. Пошагово пройдите код: F10 — шаг с обходом, F11 — шаг с заходом в метод, F5 — продолжить до следующей точки останова, F9 — поставить или снять точку останова.

Для отладки серверного кода в клиент-серверном варианте сервер 1С должен быть запущен с разрешённой отладкой (ключ -debug у службы агента). Фоновые задания отлаживаются через автоподключение в том же окне Отладка → Подключение: включите там отладку фоновых заданий, и отладчик подхватит их при старте.

Замер производительности

Если процедура не висит, а просто медленная, включите Отладка → Замер производительности и выполните операцию. Замер покажет, сколько раз выполнилась каждая строка и сколько времени она заняла. Обычно 90 % времени съедают 2-3 строки — запрос в цикле или обращение к реквизиту через точку у ссылки.

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

Чего не делать, когда нужно остановить код

  • ❌ ЗавершитьРаботуСистемы() для выхода из процедуры. Метод закрывает всё приложение пользователя. Это не способ прервать процедуру, это способ потерять его несохранённые данные.
  • ❌ Пустой Возврат вместо исключения в общем модуле. Вызывающий код не узнает, что работа не выполнена, и продолжит, например, проводку документа с пустыми данными.
  • ❌ Попытка вокруг всего метода с Сообщить(ОписаниеОшибки()). Пользователь видит текст, но в журнале регистрации следа нет, а транзакция может остаться в ошибочном состоянии.
  • ❌ Бесконечный Пока Истина Цикл без предохранителя. Если условие выхода не сработает из-за неожиданных данных, процесс будет крутиться до завершения сеанса. Счётчик на 10 000 итераций с исключением стоит одной строки.
  • ❌ Отказ = Истина без сообщения. Документ не записался, а пользователь не понимает почему и звонит вам.

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

Чем Возврат отличается от Прервать?
Прервать завершает только ближайший цикл, после него код метода продолжается. Возврат завершает весь метод целиком, где бы он ни стоял — в цикле или вне его.
Можно ли прервать процедуру, вызванную из другого модуля, так, чтобы остановился и вызывающий код?
Да, через ВызватьИсключение. Ошибка поднимается по стеку вызовов до ближайшего блока Попытка или до пользователя, и весь код выше по цепочке не выполняется.
Почему ОбработкаПрерыванияПользователя не работает на сервере?
Метод отслеживает нажатие Ctrl+Break в клиентском приложении. В серверном коде нет пользовательского ввода, поэтому для серверных операций остановку организуют через фоновое задание и его отмену.
Что будет с данными, если отменить фоновое задание посреди записи?
Незавершённая транзакция откатится, уже зафиксированные транзакции останутся. Поэтому длинную обработку делят на порции, каждая в своей транзакции.
Как задать таймаут для процедуры, чтобы она сама остановилась?
Встроенного таймаута у процедуры нет. Запомните ТекущаяУниверсальнаяДатаВМиллисекундах() в начале, сравнивайте в цикле с текущим значением и при превышении лимита выходите через Возврат или ВызватьИсключение.