Выход из цикла в 1С: чем прервать, пропустить шаг или завершить процедуру

· 9 мин

Содержание

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

Все четыре способа работают в любом цикле встроенного языка 1С:Предприятия 8.3: Пока, Для ... По и Для Каждого. Разница в том, как далеко «улетает» управление. Ниже каждый вариант с кодом, плюс то, о чём обычно вспоминают уже после ошибки: вложенные циклы, серверные вызовы внутри цикла и зависший цикл, который не отвечает.

Оператор Прервать: выйти из цикла по условию

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

Типичная задача: найти первую подходящую строку в таблице и не гонять цикл дальше.

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

Для Каждого Строка Из ТаблицаТоваров Цикл
    Если Строка.Номенклатура = Номенклатура Тогда
        НайденаСтрока = Строка;
        Прервать;
    КонецЕсли;
КонецЦикла;

Если НайденаСтрока = Неопределено Тогда
    // товара в таблице нет
КонецЕсли;

Для таблицы в 10 строк разницы не видно. Для таблицы в 200 000 строк, где нужная запись стоит третьей, выход через Прервать сэкономит почти весь проход.

Что важно знать про Прервать:

  • 🔹 он действует только на ближайший цикл, внутри которого записан;
  • 🔹 после выхода переменные цикла сохраняют значения: у Для Индекс = 0 По 9 после выхода при Индекс = 5 переменная так и останется равной 5;
  • 🔹 если оператор стоит внутри Попытка ... Исключение, цикл всё равно прервётся, обработчик исключения не сработает;
  • 🔹 вне цикла Прервать дать нельзя: платформа сообщит об ошибке ещё при проверке модуля.

Ищете строку в таблице значений? Цикл для этого не нужен: методы Найти и НайтиСтроки работают быстрее ручного перебора. Для элемента справочника есть НайтиПоКоду, НайтиПоНаименованию или запрос с условием.

Продолжить: пропустить текущий шаг и идти дальше

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

Он удобен как «ранний выход» из итерации и убирает вложенные Если:

Для Каждого Строка Из Документы Цикл
    Если Не Строка.Проведен Тогда
        Продолжить;
    КонецЕсли;
    Если Строка.Сумма = 0 Тогда
        Продолжить;
    КонецЕсли;

    ОбработатьДокумент(Строка.Ссылка);
КонецЦикла;

Без Продолжить те же условия превратились бы в лесенку из трёх уровней вложенности, где основное действие спрятано глубоко.

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

Пример ловушки:

Индекс = 0;
Пока Индекс < Массив.Количество() Цикл
    Если Массив[Индекс] = 0 Тогда
        Продолжить; // Индекс не увеличился, цикл застрянет
    КонецЕсли;
    Индекс = Индекс + 1;
КонецЦикла;

Здесь на первом же нуле программа зависнет, потому что Индекс больше не меняется.

Возврат: завершить цикл вместе с процедурой

Возврат выходит не из цикла, а из всей процедуры или функции, в которой стоит. Для цикла это самый «жёсткий» выход: всё, что записано после КонецЦикла, не выполнится.

В процедуре Возврат пишется без значения:

&НаКлиенте
Процедура ПроверитьОстатки(Склад)
    Для Каждого Строка Из ТаблицаОстатков Цикл
        Если Строка.Количество < 0 Тогда
            ПоказатьПредупреждение(, "Есть отрицательный остаток");
            Возврат;
        КонецЕсли;
    КонецЦикла;

    // эта часть выполнится, только если отрицательных остатков нет
    ПровестиОперацию(Склад);
КонецПроцедуры

В функции обязательно указывается результат:

Функция ЕстьДубль(Таблица, Код)
    Для Каждого Строка Из Таблица Цикл
        Если Строка.Код = Код Тогда
            Возврат Истина;
        КонецЕсли;
    КонецЦикла;
    Возврат Ложь;
КонецФункции

Такая запись короче, чем флаг с Прервать, и читается лучше. Если после цикла никаких действий не нужно, вместо Прервать ставьте именно Возврат.

Что выбрать: сравнение трёх операторов

ОператорЧто делаетКогда брать
ПрерватьВыходит из ближайшего цикла, дальше идёт код после негоНашли нужное, остальное в цикле не нужно, но процедура продолжается
ПродолжитьПропускает остаток тела и переходит к следующему шагуЧасть элементов надо отсеять и обработать остальные
ВозвратЗавершает процедуру или функцию целикомРезультат получен, больше делать нечего

Как выйти из вложенных циклов

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

Есть три рабочих способа.

  1. Флаг. Внутри внутреннего цикла ставим признак и после него проверяем во внешнем:
Найдено = Ложь;

Для Каждого Группа Из Группы Цикл
    Для Каждого Элемент Из Группа.Элементы Цикл
        Если Элемент.Код = ИскомыйКод Тогда
            Найдено = Истина;
            Прервать;
        КонецЕсли;
    КонецЦикла;

    Если Найдено Тогда
        Прервать;
    КонецЕсли;
КонецЦикла;
  1. Вынос в функцию. Внутренние циклы перемещают в отдельную функцию, и Возврат закрывает сразу все уровни. Этот вариант чище флага: у функции есть имя, а значит, понятно, что она ищет.
  2. Условие в заголовке внешнего цикла. Цикл Пока Не Найдено И Индекс < Количество Цикл сам остановится на следующей проверке. Подходит, когда внешний обход всё равно идёт по индексу.

Оператор Перейти с меткой тоже подойдёт, но в современном коде его избегают: он ломает порядок чтения и затрудняет отладку.

Исключение как аварийная остановка цикла

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

Для Каждого Строка Из Реестр Цикл
    Если Не ЗаполненКонтрагент(Строка) Тогда
        ВызватьИсключение "Не заполнен контрагент в строке "
            + Формат(Строка.НомерСтроки, "ЧГ=0");
    КонецЕсли;
    ЗаписатьСтроку(Строка);
КонецЦикла;

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

Чтобы продолжить работу после сбоя на одной строке, ловите исключение внутри цикла:

Для Каждого Файл Из Файлы Цикл
    Попытка
        ЗагрузитьФайл(Файл);
    Исключение
        ЗаписатьЖурнал(Файл, ОписаниеОшибки());
        Продолжить;
    КонецПопытки;
КонецЦикла;

Так один битый файл из ста не обрушит всю загрузку, а причина сохранится в журнале. Функция ОписаниеОшибки() возвращает текст последней перехваченной ошибки.

Выбор между двумя схемами простой:

  • ✅ данные должны остаться согласованными, всё или ничего — исключение наружу, без перехвата в цикле;
  • ✅ строки независимы друг от друга — перехват внутри цикла и Продолжить.

Не оборачивайте в Попытка весь цикл «на всякий случай» с пустым обработчиком. Ошибка исчезнет, а данные окажутся записаны частично, и найти причину потом будет трудно.

Клиент и сервер: чем отличается остановка цикла

Синтаксис Прервать, Продолжить и Возврат на клиенте и на сервере одинаковый. Отличается цена каждого шага.

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

Плохо:

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

Хорошо: передать на сервер весь список целиком и крутить цикл уже там.

Ещё несколько отличий:

  • 🔸 клиентский цикл блокирует интерфейс сеанса, пока идёт; пользователь не сможет ничего нажать, кроме системных действий;
  • 🔸 на сервере долгий цикл удерживает рабочий процесс и, если внутри него идёт запись в транзакции, блокирует данные для других пользователей;
  • 🔸 для тяжёлых расчётов цикл выносят в фоновое задание, и тогда прервать работу можно отменой задания, а не выходом из цикла;
  • 🔸 в цикле по данным запроса используйте Пока Выборка.Следующий() Цикл; выход из него через Прервать работает так же, как в обычном.

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

Что делать, если цикл завис и не останавливается

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

Порядок действий:

  1. Если цикл крутится в отладке из Конфигуратора, нажмите паузу или прерывание в панели отладки и посмотрите в точке остановки значения переменных условия.
  2. Если зависло клиентское приложение у пользователя, закройте сеанс и откройте базу снова. Несохранённые данные пропадут, поэтому сначала убедитесь, что окно действительно не отвечает, а не просто выполняет долгую операцию.
  3. Если цикл на сервере держит рабочий процесс, завершите сеанс пользователя в консоли кластера серверов. Завершение сеанса обрывает работу человека и откатывает его незавершённую транзакцию, поэтому лучше делать это в нерабочее время или предупредив пользователя.
  4. Исправьте причину: добавьте предельное число итераций или выход по условию.

Защита от бесконечности в самом коде:

МаксИтераций = 100000;
Счетчик = 0;

Пока НеЗавершено Цикл
    Счетчик = Счетчик + 1;
    Если Счетчик > МаксИтераций Тогда
        ВызватьИсключение "Превышено допустимое число итераций";
    КонецЕсли;
    // тело цикла
КонецЦикла;

Для циклов, зависящих от внешних данных (ожидание ответа, обход иерархии без контроля глубины), такой предохранитель обязателен. Без него одно некорректное значение в данных остановит работу всей базы.

Как писать циклы, чтобы потом не останавливать их аварийно

Несколько привычек, которые заметно снижают число зависаний и медленных процедур.

  • 🔹 Не меняйте коллекцию, по которой идёте. Удаление строк внутри Для Каждого по той же таблице приводит к пропускам или ошибкам. Идите по индексам от последней строки к первой или сначала соберите нужные строки в массив, а удаляйте уже после цикла.
  • 🔹 Условие выхода держите рядом с началом. Ранние Продолжить и Прервать читаются лучше глубокой вложенности.
  • 🔹 Вынесите из цикла всё лишнее. Вызов Количество() у большого массива, обращение к реквизиту через точку у ссылки, запрос внутри цикла: каждое такое действие умножается на число итераций.
  • 🔹 Запрос внутри цикла замените одним запросом с соединением или условием В (&Массив). Это главный выигрыш по скорости, и выходить из цикла после этого часто вообще не понадобится.
  • 🔹 Не запихивайте в цикл по 100 000 записей запись объектов по одному, если можно обработать пакетом через регистр или набор записей.

Границы Для Индекс = 0 По N вычисляются один раз до начала цикла. Если внутри вы добавляете или удаляете элементы, число проходов от этого не изменится, и это тоже источник ошибок «вышли за границы массива».

Проверьте перед запуском

Перед запуском обработки с циклом
Выполнено: 0 / 5

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

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