Прерывание и выход из цикла во встроенном языке 1С

· 9 мин

Содержание

Досрочно выйти из цикла в 1С позволяет оператор Прервать — он сразу передаёт управление за строку КонецЦикла. Пропустить текущую итерацию и перейти к следующей — оператор Продолжить. Это работает одинаково во всех трёх видах циклов: Для … По, Для Каждого … Из и Пока.

Многие путают Прервать с Возврат и не замечают главную ловушку: Прервать выходит только из того цикла, внутри которого он стоит. Во вложенной конструкции внешний цикл продолжит работу. Ниже — все рабочие способы завершить цикл правильно: по найденному значению, по флагу, при ошибке, по таймауту, а также как остановить цикл в фоновом задании.

Операторы Прервать и Продолжить

Прервать обрывает цикл целиком. Типичный сценарий — поиск: нашли нужный элемент, дальше перебирать бессмысленно.

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

Продолжить не выходит из цикла, а перескакивает к следующей итерации. Удобно, чтобы отфильтровать лишние элементы без лишней вложенности Если.

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

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

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

Вид циклаЧем управляетсяКак завершить досрочно
Для Сч = 1 По N Циклсчётчиком от и доПрервать по условию внутри
Для Каждого Эл Из Коллекция Циклперебором готовой коллекцииПрервать, как только нашли нужное
Пока Условие Цикллогическим условиемПрервать или сброс условия (флаг)

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

Выход из цикла по условию через флаг

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

ПродолжатьОбработку = Истина;
Пока ПродолжатьОбработку Цикл
    Порция = ПолучитьСледующуюПорцию();
    Если Порция.Количество() = 0 Тогда
        ПродолжатьОбработку = Ложь; // данные кончились — следующий проход не начнётся
        Продолжить;
    КонецЕсли;
    ОбработатьПорцию(Порция);
КонецЦикла;

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

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

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

Прерывание цикла при ошибке

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

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

Своё исключение, чтобы прервать обработку по бизнес-условию, возбуждают оператором ВызватьИсключение. Но учтите: исключение, возникшее внутри цикла и не перехваченное Попыткой, завершает не только цикл, а всю процедуру. Если нужно именно остановить цикл и продолжить работу дальше — ловите исключение здесь же.

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

Как остановить цикл в фоновом задании

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

Снаружи — методом Отменить(). Из сеанса, который запустил задание, находят его по идентификатору и отменяют:

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

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

Изнутри — проверкой флага. Более аккуратный приём: задание само периодически смотрит на общий признак остановки (например, значение в константе или регистре сведений) и корректно завершает цикл через Прервать.

Для Каждого Элемент Из БольшаяВыборка Цикл
    Если Сч % 100 = 0 И ТребуетсяОстановка() Тогда
        Прервать; // вышли сами, записанное сохранено
    КонецЕсли;
    ОбработатьЭлемент(Элемент);
    Сч = Сч + 1;
КонецЦикла;

Проверку флага не ставьте на каждой итерации — чтение из базы само по себе стоит времени. Достаточно заглядывать раз в сотню-другую элементов.

Длинные циклы и большие объёмы данных

Самая частая причина «зависшего» цикла — не отсутствие Прервать, а запрос или обращение к базе внутри тела. Цикл на 10 000 элементов, где на каждой итерации выполняется запрос, превращается в 10 000 запросов.

Правильный подход — получить все данные одним запросом до цикла, а внутри перебирать уже готовую выборку:

Запрос = Новый Запрос;
Запрос.Текст =
    "ВЫБРАТЬ Ссылка, Контрагент, СуммаДокумента
    |ИЗ Документ.РеализацияТоваровУслуг
    |ГДЕ Проведен";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    ОбработатьДокумент(Выборка.Ссылка);
КонецЦикла;

Отдельная задача — ограничить цикл по времени, а не по количеству элементов. Это нужно, когда обработка идёт по внешнему источнику с непредсказуемой скоростью и нельзя висеть дольше, скажем, тридцати секунд. Засеките момент старта в миллисекундах и сравнивайте с текущим на каждой итерации:

ЛимитМиллисекунд = 30000;
НачалоРаботы = ТекущаяУниверсальнаяДатаВМиллисекундах();
Для Каждого Элемент Из Выборка Цикл
    Если ТекущаяУниверсальнаяДатаВМиллисекундах() - НачалоРаботы > ЛимитМиллисекунд Тогда
        Прервать; // уложились во время — остаток обработаем следующим запуском
    КонецЕсли;
    ОбработатьЭлемент(Элемент);
КонецЦикла;

Такой выход по таймауту удобно сочетать с порционной обработкой: за один сеанс цикл берёт столько, сколько успевает, а необработанный остаток помечает к следующему проходу.

Несколько приёмов, которые ускоряют и сокращают циклы:

  • 🎯 Отбирайте нужное в запросе (секция ГДЕ), а не перебором с Продолжить — база сделает это быстрее.
  • 🚪 Как только набрали достаточно данных — выходите через Прервать, не досматривайте всю коллекцию.
  • 📦 Данные измеряются сотнями тысяч строк — разбивайте обработку на порции и фиксируйте результат частями, чтобы не держать одну гигантскую транзакцию.
  • 🔁 Избегайте вложенных циклов по большим коллекциям: два перебора по 1000 строк — это миллион итераций.

Реакция на прерывание пользователем

В длинной интерактивной обработке пользователь должен иметь возможность остановить процесс. Для этого есть глобальный метод ОбработкаПрерыванияПользователя(). Он проверяет, не нажал ли пользователь клавишу прерывания, и если да — аккуратно возбуждает исключение прерывания.

Для Каждого Строка Из БольшаяТаблица Цикл
    ОбработкаПрерыванияПользователя(); // даёт шанс остановить цикл вручную
    ВыполнитьДлительнуюОперацию(Строка);
КонецЦикла;

Без вызова этого метода тяжёлый цикл блокирует интерфейс, и прервать его можно только снятием сеанса. Вызывайте ОбработкаПрерыванияПользователя() в начале тела — тогда у пользователя появляется контролируемая точка выхода. Чтобы прерывание не выглядело как сбой, оберните цикл в Попытку и покажите понятное сообщение об отмене.

Частые ошибки при выходе из цикла

🔻 Ждут, что Прервать выйдет из всех вложенных циклов. Он выходит только из своего. Для выхода из внешнего нужен флаг или вынос логики в отдельную функцию с Возврат.

🔻 Пытаются идти по коллекции в обратную сторону через Для … По. В 1С шаг цикла Для всегда +1, строка Для Сч = 10 По 0 Цикл не выполнится ни разу. Чтобы обойти коллекцию с конца — например, при удалении строк — считайте индекс от длины:

Количество = Таблица.Количество();
Для Сч = 1 По Количество Цикл
    Индекс = Количество - Сч; // идём от последнего элемента к первому
    Если Таблица[Индекс].Пометка Тогда
        Таблица.Удалить(Индекс);
    КонецЕсли;
КонецЦикла;

🔻 Удаляют элементы внутри Для Каждого. Изменение коллекции во время её перебора приводит к пропуску строк или ошибке. Удаляйте только по индексу с конца, как в примере выше.

🔻 Путают Прервать и Возврат. Возврат завершает всю процедуру или функцию, а не цикл. Если после цикла в этой же процедуре ещё есть код, Возврат его не выполнит.

🔻 Строят бесконечный Пока. Условие выхода не меняется в теле — цикл крутится вечно. Всегда убеждайтесь, что переменная из условия где-то обновляется.

Проверьте себя
Из какого цикла выйдет оператор `Прервать`, стоящий во внутреннем из двух вложенных циклов?
Какой оператор завершит всю процедуру, а не только цикл?
Сколько раз выполнится тело цикла `Для Сч = 5 По 1 Цикл`?

Рекурсия и обход коллекций вместо ручного цикла

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

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

Процедура ОбойтиДерево(Строки)
    Для Каждого Строка Из Строки Цикл
        ОбработатьУзел(Строка);
        Если Строка.Строки.Количество() > 0 Тогда
            ОбойтиДерево(Строка.Строки); // спускаемся на уровень ниже
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры;

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

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

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

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

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