Выход из цикла в 1С: чем прервать, пропустить шаг или завершить процедуру
Содержание
Остановить цикл в 1С можно оператором Прервать: он выходит из ближайшего цикла и передаёт управление на строку после КонецЦикла. Если нужно пропустить только текущий шаг, ставят Продолжить. Если вместе с циклом надо завершить и всю процедуру или функцию, используют Возврат. А когда что-то пошло совсем не так, цикл прерывают исключением через ВызватьИсключение.
Все четыре способа работают в любом цикле встроенного языка 1С:Предприятия 8.3: Пока, Для ... По и Для Каждого. Разница в том, как далеко «улетает» управление. Ниже каждый вариант с кодом, плюс то, о чём обычно вспоминают уже после ошибки: вложенные циклы, серверные вызовы внутри цикла и зависший цикл, который не отвечает.
Оператор Прервать: выйти из цикла по условию
Прервать завершает цикл немедленно. Строки тела после него не выполняются, счётчик больше не увеличивается, а программа продолжается с первой команды за КонецЦикла.
Типичная задача: найти первую подходящую строку в таблице и не гонять цикл дальше.
НайденаСтрока = Неопределено;
Для Каждого Строка Из ТаблицаТоваров Цикл
Если Строка.Номенклатура = Номенклатура Тогда
НайденаСтрока = Строка;
Прервать;
КонецЕсли;
КонецЦикла;
Если НайденаСтрока = Неопределено Тогда
// товара в таблице нет
КонецЕсли;
Для таблицы в 10 строк разницы не видно. Для таблицы в 200 000 строк, где нужная запись стоит третьей, выход через Прервать сэкономит почти весь проход.
Что важно знать про Прервать:
- 🔹 он действует только на ближайший цикл, внутри которого записан;
- 🔹 после выхода переменные цикла сохраняют значения: у
Для Индекс = 0 По 9после выхода приИндекс = 5переменная так и останется равной 5; - 🔹 если оператор стоит внутри
Попытка ... Исключение, цикл всё равно прервётся, обработчик исключения не сработает; - 🔹 вне цикла
Прерватьдать нельзя: платформа сообщит об ошибке ещё при проверке модуля.
Ищете строку в таблице значений? Цикл для этого не нужен: методы Найти и НайтиСтроки работают быстрее ручного перебора. Для элемента справочника есть НайтиПоКоду, НайтиПоНаименованию или запрос с условием.
Продолжить: пропустить текущий шаг и идти дальше
Продолжить не выходит из цикла, а перескакивает в его конец. Для Пока управление вернётся к проверке условия, для Для ... По счётчик увеличится на шаг, для Для Каждого возьмётся следующий элемент.
Он удобен как «ранний выход» из итерации и убирает вложенные Если:
Для Каждого Строка Из Документы Цикл
Если Не Строка.Проведен Тогда
Продолжить;
КонецЕсли;
Если Строка.Сумма = 0 Тогда
Продолжить;
КонецЕсли;
ОбработатьДокумент(Строка.Ссылка);
КонецЦикла;
Без Продолжить те же условия превратились бы в лесенку из трёх уровней вложенности, где основное действие спрятано глубоко.
В цикле Пока будьте осторожны: если изменение переменной условия стоит в конце тела, Продолжить его пропустит, и цикл станет бесконечным. Увеличивайте счётчик до Продолжить или переписывайте цикл на Для.
Пример ловушки:
Индекс = 0;
Пока Индекс < Массив.Количество() Цикл
Если Массив[Индекс] = 0 Тогда
Продолжить; // Индекс не увеличился, цикл застрянет
КонецЕсли;
Индекс = Индекс + 1;
КонецЦикла;
Здесь на первом же нуле программа зависнет, потому что Индекс больше не меняется.
Возврат: завершить цикл вместе с процедурой
Возврат выходит не из цикла, а из всей процедуры или функции, в которой стоит. Для цикла это самый «жёсткий» выход: всё, что записано после КонецЦикла, не выполнится.
В процедуре Возврат пишется без значения:
&НаКлиенте
Процедура ПроверитьОстатки(Склад)
Для Каждого Строка Из ТаблицаОстатков Цикл
Если Строка.Количество < 0 Тогда
ПоказатьПредупреждение(, "Есть отрицательный остаток");
Возврат;
КонецЕсли;
КонецЦикла;
// эта часть выполнится, только если отрицательных остатков нет
ПровестиОперацию(Склад);
КонецПроцедуры
В функции обязательно указывается результат:
Функция ЕстьДубль(Таблица, Код)
Для Каждого Строка Из Таблица Цикл
Если Строка.Код = Код Тогда
Возврат Истина;
КонецЕсли;
КонецЦикла;
Возврат Ложь;
КонецФункции
Такая запись короче, чем флаг с Прервать, и читается лучше. Если после цикла никаких действий не нужно, вместо Прервать ставьте именно Возврат.
Что выбрать: сравнение трёх операторов
| Оператор | Что делает | Когда брать |
|---|---|---|
Прервать | Выходит из ближайшего цикла, дальше идёт код после него | Нашли нужное, остальное в цикле не нужно, но процедура продолжается |
Продолжить | Пропускает остаток тела и переходит к следующему шагу | Часть элементов надо отсеять и обработать остальные |
Возврат | Завершает процедуру или функцию целиком | Результат получен, больше делать нечего |
Как выйти из вложенных циклов
В языке 1С нет оператора вида «выйти из двух циклов сразу». Прервать завершает только внутренний цикл, и внешний продолжит работу. Это самая частая причина, по которой «выход не срабатывает».
Есть три рабочих способа.
- Флаг. Внутри внутреннего цикла ставим признак и после него проверяем во внешнем:
Найдено = Ложь;
Для Каждого Группа Из Группы Цикл
Для Каждого Элемент Из Группа.Элементы Цикл
Если Элемент.Код = ИскомыйКод Тогда
Найдено = Истина;
Прервать;
КонецЕсли;
КонецЦикла;
Если Найдено Тогда
Прервать;
КонецЕсли;
КонецЦикла;
- Вынос в функцию. Внутренние циклы перемещают в отдельную функцию, и
Возвратзакрывает сразу все уровни. Этот вариант чище флага: у функции есть имя, а значит, понятно, что она ищет. - Условие в заголовке внешнего цикла. Цикл
Пока Не Найдено И Индекс < Количество Циклсам остановится на следующей проверке. Подходит, когда внешний обход всё равно идёт по индексу.
Оператор Перейти с меткой тоже подойдёт, но в современном коде его избегают: он ломает порядок чтения и затрудняет отладку.
Исключение как аварийная остановка цикла
Когда продолжать нельзя вовсе, цикл останавливают исключением. Оператор ВызватьИсключение выбрасывает ошибку с текстом, и она «проскакивает» через все циклы и процедуры вверх по стеку, пока её не поймает Попытка ... Исключение.
Для Каждого Строка Из Реестр Цикл
Если Не ЗаполненКонтрагент(Строка) Тогда
ВызватьИсключение "Не заполнен контрагент в строке "
+ Формат(Строка.НомерСтроки, "ЧГ=0");
КонецЕсли;
ЗаписатьСтроку(Строка);
КонецЦикла;
Если исключение нигде не перехвачено, пользователь увидит окно с текстом ошибки, а операция (например, проведение документа) откатится вместе с транзакцией. Это нужное поведение для обработки данных: лучше остановиться, чем записать половину реестра.
Чтобы продолжить работу после сбоя на одной строке, ловите исключение внутри цикла:
Для Каждого Файл Из Файлы Цикл
Попытка
ЗагрузитьФайл(Файл);
Исключение
ЗаписатьЖурнал(Файл, ОписаниеОшибки());
Продолжить;
КонецПопытки;
КонецЦикла;
Так один битый файл из ста не обрушит всю загрузку, а причина сохранится в журнале. Функция ОписаниеОшибки() возвращает текст последней перехваченной ошибки.
Выбор между двумя схемами простой:
- ✅ данные должны остаться согласованными, всё или ничего — исключение наружу, без перехвата в цикле;
- ✅ строки независимы друг от друга — перехват внутри цикла и
Продолжить.
Не оборачивайте в Попытка весь цикл «на всякий случай» с пустым обработчиком. Ошибка исчезнет, а данные окажутся записаны частично, и найти причину потом будет трудно.
Клиент и сервер: чем отличается остановка цикла
Синтаксис Прервать, Продолжить и Возврат на клиенте и на сервере одинаковый. Отличается цена каждого шага.
Самая дорогая ошибка: вызов сервера внутри клиентского цикла. Каждая итерация, в которой выполняется серверный вызов, превращается в обращение по сети. Сто итераций дают сто обращений, и форма замирает.
Плохо:
&НаКлиенте
Процедура ОбработатьСписок()
Для Каждого Элемент Из СписокСсылок Цикл
ПолучитьДанныеНаСервере(Элемент); // серверный вызов на каждом шаге
КонецЦикла;
КонецПроцедуры
Хорошо: передать на сервер весь список целиком и крутить цикл уже там.
Ещё несколько отличий:
- 🔸 клиентский цикл блокирует интерфейс сеанса, пока идёт; пользователь не сможет ничего нажать, кроме системных действий;
- 🔸 на сервере долгий цикл удерживает рабочий процесс и, если внутри него идёт запись в транзакции, блокирует данные для других пользователей;
- 🔸 для тяжёлых расчётов цикл выносят в фоновое задание, и тогда прервать работу можно отменой задания, а не выходом из цикла;
- 🔸 в цикле по данным запроса используйте
Пока Выборка.Следующий() Цикл; выход из него черезПрерватьработает так же, как в обычном.
Для длительных клиентских операций полезна процедура ОбработкаПрерыванияПользователя(). Вызванная в теле цикла, она даёт платформе возможность обработать прерывание: пользователь останавливает обработку сочетанием Ctrl+Break, и долгая обработка перестаёт быть «глухой».
Что делать, если цикл завис и не останавливается
Бесконечный цикл почти всегда вызван одним из трёх сценариев: условие Пока никогда не становится ложным, переменная не меняется из-за Продолжить (см. выше), либо цикл бежит по коллекции, которую сам же наполняет.
Порядок действий:
- Если цикл крутится в отладке из Конфигуратора, нажмите паузу или прерывание в панели отладки и посмотрите в точке остановки значения переменных условия.
- Если зависло клиентское приложение у пользователя, закройте сеанс и откройте базу снова. Несохранённые данные пропадут, поэтому сначала убедитесь, что окно действительно не отвечает, а не просто выполняет долгую операцию.
- Если цикл на сервере держит рабочий процесс, завершите сеанс пользователя в консоли кластера серверов. Завершение сеанса обрывает работу человека и откатывает его незавершённую транзакцию, поэтому лучше делать это в нерабочее время или предупредив пользователя.
- Исправьте причину: добавьте предельное число итераций или выход по условию.
Защита от бесконечности в самом коде:
МаксИтераций = 100000;
Счетчик = 0;
Пока НеЗавершено Цикл
Счетчик = Счетчик + 1;
Если Счетчик > МаксИтераций Тогда
ВызватьИсключение "Превышено допустимое число итераций";
КонецЕсли;
// тело цикла
КонецЦикла;
Для циклов, зависящих от внешних данных (ожидание ответа, обход иерархии без контроля глубины), такой предохранитель обязателен. Без него одно некорректное значение в данных остановит работу всей базы.
Как писать циклы, чтобы потом не останавливать их аварийно
Несколько привычек, которые заметно снижают число зависаний и медленных процедур.
- 🔹 Не меняйте коллекцию, по которой идёте. Удаление строк внутри
Для Каждогопо той же таблице приводит к пропускам или ошибкам. Идите по индексам от последней строки к первой или сначала соберите нужные строки в массив, а удаляйте уже после цикла. - 🔹 Условие выхода держите рядом с началом. Ранние
ПродолжитьиПрерватьчитаются лучше глубокой вложенности. - 🔹 Вынесите из цикла всё лишнее. Вызов
Количество()у большого массива, обращение к реквизиту через точку у ссылки, запрос внутри цикла: каждое такое действие умножается на число итераций. - 🔹 Запрос внутри цикла замените одним запросом с соединением или условием
В (&Массив). Это главный выигрыш по скорости, и выходить из цикла после этого часто вообще не понадобится. - 🔹 Не запихивайте в цикл по 100 000 записей запись объектов по одному, если можно обработать пакетом через регистр или набор записей.
Границы Для Индекс = 0 По N вычисляются один раз до начала цикла. Если внутри вы добавляете или удаляете элементы, число проходов от этого не изменится, и это тоже источник ошибок «вышли за границы массива».
Проверьте перед запуском
Частые вопросы
Можно ли прервать цикл `Для Каждого` по коллекции из внешней функции?
Прервать.Что произойдёт с переменной-счётчиком после `Прервать`?
Работает ли `Продолжить` в цикле `Пока`?
Откатит ли `Прервать` уже сделанные в цикле записи?
Прервать только выходит из цикла. Откат происходит только при исключении внутри транзакции или при явном вызове ОтменитьТранзакцию().Есть ли в 1С оператор выхода сразу из нескольких циклов?
Возврат или условие в заголовке внешнего цикла.