Прерывание и выход из цикла во встроенном языке 1С
Содержание
Досрочно выйти из цикла в 1С позволяет оператор Прервать — он сразу передаёт управление за строку КонецЦикла. Пропустить текущую итерацию и перейти к следующей — оператор Продолжить. Это работает одинаково во всех трёх видах циклов: Для … По, Для Каждого … Из и Пока.
Многие путают Прервать с Возврат и не замечают главную ловушку: Прервать выходит только из того цикла, внутри которого он стоит. Во вложенной конструкции внешний цикл продолжит работу. Ниже — все рабочие способы завершить цикл правильно: по найденному значению, по флагу, при ошибке, по таймауту, а также как остановить цикл в фоновом задании.
Операторы Прервать и Продолжить
Прервать обрывает цикл целиком. Типичный сценарий — поиск: нашли нужный элемент, дальше перебирать бессмысленно.
НайденнаяСтрока = Неопределено;
Для Каждого Строка Из Таблица Цикл
Если Строка.Код = ИскомыйКод Тогда
НайденнаяСтрока = Строка;
Прервать; // нашли — выходим, остальные строки не нужны
КонецЕсли;
КонецЦикла;
Продолжить не выходит из цикла, а перескакивает к следующей итерации. Удобно, чтобы отфильтровать лишние элементы без лишней вложенности Если.
Для Каждого Строка Из Таблица Цикл
Если Строка.Сумма = 0 Тогда
Продолжить; // пустые строки пропускаем
КонецЕсли;
ОбработатьСтроку(Строка);
КонецЦикла;
В цикле Пока будьте внимательны с Продолжить: если счётчик увеличивается в самом конце тела, Продолжить его перескочит и вы получите бесконечный цикл. Увеличивайте счётчик до оператора Продолжить.
Оба оператора работают в любом из трёх видов циклов, но досрочный выход в каждом организуется по-своему:
| Вид цикла | Чем управляется | Как завершить досрочно |
|---|---|---|
Для Сч = 1 По N Цикл | счётчиком от и до | Прервать по условию внутри |
Для Каждого Эл Из Коллекция Цикл | перебором готовой коллекции | Прервать, как только нашли нужное |
Пока Условие Цикл | логическим условием | Прервать или сброс условия (флаг) |
У цикла Пока есть две точки выхода: сам оператор Прервать и изменение условия в заголовке. У Для Каждого условие вычислять не нужно — он сам останавливается, когда коллекция кончилась, и его чаще всего обрывают именно Прервать при досрочной находке.
Выход из цикла по условию через флаг
Когда условие выхода сложное или вычисляется в нескольких местах, заводят переменную-флаг. Это классический способ завершить цикл Пока, где нет готовой коллекции для перебора.
ПродолжатьОбработку = Истина;
Пока ПродолжатьОбработку Цикл
Порция = ПолучитьСледующуюПорцию();
Если Порция.Количество() = 0 Тогда
ПродолжатьОбработку = Ложь; // данные кончились — следующий проход не начнётся
Продолжить;
КонецЕсли;
ОбработатьПорцию(Порция);
КонецЦикла;
Флаг незаменим, когда нужно выйти сразу из двух вложенных циклов. Одной команды для выхода из всех циклов разом в языке 1С нет. Поэтому внутренний цикл прерывают Прервать, поднимают флаг, а сразу после внутреннего цикла проверяют его и прерывают внешний.
НайденоСовпадение = Ложь;
Для Каждого Заказ Из Заказы Цикл
Для Каждого ТоварнаяСтрока Из Заказ.Товары Цикл
Если ТоварнаяСтрока.Номенклатура = ИскомаяНоменклатура Тогда
НайденоСовпадение = Истина;
Прервать; // выходим из внутреннего цикла
КонецЕсли;
КонецЦикла;
Если НайденоСовпадение Тогда
Прервать; // и из внешнего
КонецЕсли;
КонецЦикла;
Второй приём для той же задачи — вынести вложенный перебор в отдельную функцию и выйти из неё оператором Возврат. Тогда вместо двух флагов получается одна точка выхода, а код читается проще.
Прерывание цикла при ошибке
Если в теле цикла возможен сбой — обращение к внешнему ресурсу, разбор файла, выполнение запроса, — одна ошибка не должна ронять всю обработку. Оберните потенциально опасный код в Попытка … Исключение … КонецПопытки и решите, что делать: пропустить проблемный элемент через Продолжить или остановить всё через Прервать.
ОбработаноУспешно = 0;
Для Каждого Файл Из СписокФайлов Цикл
Попытка
ЗагрузитьФайл(Файл);
ОбработаноУспешно = ОбработаноУспешно + 1;
Исключение
ЗаписьЖурналаРегистрации("Загрузка данных",
УровеньЖурналаРегистрации.Ошибка, , , ОписаниеОшибки());
Продолжить; // сбойный файл пропускаем, остальные обрабатываем
КонецПопытки;
КонецЦикла;
Своё исключение, чтобы прервать обработку по бизнес-условию, возбуждают оператором ВызватьИсключение. Но учтите: исключение, возникшее внутри цикла и не перехваченное Попыткой, завершает не только цикл, а всю процедуру. Если нужно именно остановить цикл и продолжить работу дальше — ловите исключение здесь же.
Не оставляйте блок Исключение пустым. «Проглоченная» ошибка превращается в молчаливую потерю данных: часть элементов не обработается, а пользователь об этом не узнает. Пишите в журнал регистрации или накапливайте список проблемных элементов для отчёта.
Как остановить цикл в фоновом задании
У фонового задания нет пользователя, который нажмёт «Отмена», поэтому ОбработкаПрерыванияПользователя() здесь не работает
. Остановить длинный цикл в фоне можно двумя путями.
Снаружи — методом Отменить(). Из сеанса, который запустил задание, находят его по идентификатору и отменяют:
Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(ИдентификаторЗадания);
Если Задание <> Неопределено Тогда
Задание.Отменить();
КонецЕсли;
Это жёсткая остановка: платформа прерывает выполнение, и незавершённая транзакция откатывается. Если в середине цикла что-то уже записано вне транзакции, данные могут остаться в промежуточном состоянии.
Изнутри — проверкой флага. Более аккуратный приём: задание само периодически смотрит на общий признак остановки (например, значение в константе или регистре сведений) и корректно завершает цикл через Прервать.
Для Каждого Элемент Из БольшаяВыборка Цикл
Если Сч % 100 = 0 И ТребуетсяОстановка() Тогда
Прервать; // вышли сами, записанное сохранено
КонецЕсли;
ОбработатьЭлемент(Элемент);
Сч = Сч + 1;
КонецЦикла;
Проверку флага не ставьте на каждой итерации — чтение из базы само по себе стоит времени. Достаточно заглядывать раз в сотню-другую элементов.
Длинные циклы и большие объёмы данных
Самая частая причина «зависшего» цикла — не отсутствие Прервать, а запрос или обращение к базе внутри тела. Цикл на 10 000 элементов, где на каждой итерации выполняется запрос, превращается в 10 000 запросов.
Правильный подход — получить все данные одним запросом до цикла, а внутри перебирать уже готовую выборку:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Ссылка, Контрагент, СуммаДокумента
|ИЗ Документ.РеализацияТоваровУслуг
|ГДЕ Проведен";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ОбработатьДокумент(Выборка.Ссылка);
КонецЦикла;
Отдельная задача — ограничить цикл по времени, а не по количеству элементов. Это нужно, когда обработка идёт по внешнему источнику с непредсказуемой скоростью и нельзя висеть дольше, скажем, тридцати секунд. Засеките момент старта в миллисекундах и сравнивайте с текущим на каждой итерации:
ЛимитМиллисекунд = 30000;
НачалоРаботы = ТекущаяУниверсальнаяДатаВМиллисекундах();
Для Каждого Элемент Из Выборка Цикл
Если ТекущаяУниверсальнаяДатаВМиллисекундах() - НачалоРаботы > ЛимитМиллисекунд Тогда
Прервать; // уложились во время — остаток обработаем следующим запуском
КонецЕсли;
ОбработатьЭлемент(Элемент);
КонецЦикла;
Такой выход по таймауту удобно сочетать с порционной обработкой: за один сеанс цикл берёт столько, сколько успевает, а необработанный остаток помечает к следующему проходу.
Несколько приёмов, которые ускоряют и сокращают циклы:
- 🎯 Отбирайте нужное в запросе (секция
ГДЕ), а не перебором сПродолжить— база сделает это быстрее. - 🚪 Как только набрали достаточно данных — выходите через
Прервать, не досматривайте всю коллекцию. - 📦 Данные измеряются сотнями тысяч строк — разбивайте обработку на порции и фиксируйте результат частями, чтобы не держать одну гигантскую транзакцию.
- 🔁 Избегайте вложенных циклов по большим коллекциям: два перебора по 1000 строк — это миллион итераций.
Реакция на прерывание пользователем
В длинной интерактивной обработке пользователь должен иметь возможность остановить процесс. Для этого есть глобальный метод ОбработкаПрерыванияПользователя(). Он проверяет, не нажал ли пользователь клавишу прерывания, и если да — аккуратно возбуждает исключение прерывания.
Для Каждого Строка Из БольшаяТаблица Цикл
ОбработкаПрерыванияПользователя(); // даёт шанс остановить цикл вручную
ВыполнитьДлительнуюОперацию(Строка);
КонецЦикла;
Без вызова этого метода тяжёлый цикл блокирует интерфейс, и прервать его можно только снятием сеанса. Вызывайте ОбработкаПрерыванияПользователя() в начале тела — тогда у пользователя появляется контролируемая точка выхода. Чтобы прерывание не выглядело как сбой, оберните цикл в Попытку и покажите понятное сообщение об отмене.
Частые ошибки при выходе из цикла
🔻 Ждут, что Прервать выйдет из всех вложенных циклов. Он выходит только из своего. Для выхода из внешнего нужен флаг или вынос логики в отдельную функцию с Возврат.
🔻 Пытаются идти по коллекции в обратную сторону через Для … По. В 1С шаг цикла Для всегда +1, строка Для Сч = 10 По 0 Цикл не выполнится ни разу. Чтобы обойти коллекцию с конца — например, при удалении строк — считайте индекс от длины:
Количество = Таблица.Количество();
Для Сч = 1 По Количество Цикл
Индекс = Количество - Сч; // идём от последнего элемента к первому
Если Таблица[Индекс].Пометка Тогда
Таблица.Удалить(Индекс);
КонецЕсли;
КонецЦикла;
🔻 Удаляют элементы внутри Для Каждого. Изменение коллекции во время её перебора приводит к пропуску строк или ошибке. Удаляйте только по индексу с конца, как в примере выше.
🔻 Путают Прервать и Возврат. Возврат завершает всю процедуру или функцию, а не цикл. Если после цикла в этой же процедуре ещё есть код, Возврат его не выполнит.
🔻 Строят бесконечный Пока. Условие выхода не меняется в теле — цикл крутится вечно. Всегда убеждайтесь, что переменная из условия где-то обновляется.
Рекурсия и обход коллекций вместо ручного цикла
Иногда цикл вообще не нужен. Для перебора таблицы значений, массива или выборки запроса естественнее Для Каждого — он сам отслеживает границы, и ошибиться с индексом невозможно.
Для иерархических данных — дерева значений, групп справочника, подчинённых элементов — вместо ручного управления вложенными циклами применяют рекурсию: процедура обходит узел и вызывает саму себя для каждого потомка.
Процедура ОбойтиДерево(Строки)
Для Каждого Строка Из Строки Цикл
ОбработатьУзел(Строка);
Если Строка.Строки.Количество() > 0 Тогда
ОбойтиДерево(Строка.Строки); // спускаемся на уровень ниже
КонецЕсли;
КонецЦикла;
КонецПроцедуры;
У рекурсии есть предел глубины вложенности. Для действительно глубоких структур (тысячи уровней) надёжнее обход через явный стек — массив, в который складываются необработанные узлы, с циклом Пока по нему. Для типовых справочников и деревьев рекурсии хватает с запасом.
Перед тем как писать цикл, спросите себя, нужен ли он вообще. Подсчёт сумм и остатков делает запрос с группировкой, отбор строк — секция ГДЕ, а заполнение одной таблицы из другой — соединение в запросе. Цикл в коде оправдан там, где на каждом элементе выполняется действие, которое нельзя выразить запросом: запись документа, вызов внешнего сервиса, формирование файла.
Если задачу всё же решает один запрос с иерархией или соединением — предпочтите его циклу. Платформа обрабатывает данные пачкой эффективнее, чем построчный перебор в коде, и такой «цикл» не нужно ни прерывать, ни оптимизировать.
Частые вопросы
Чем `Прервать` отличается от `Возврат`?
Прервать выходит только из текущего цикла, а выполнение процедуры продолжается со строки после КонецЦикла. Возврат завершает всю процедуру или функцию целиком, включая код после цикла.Как выйти сразу из нескольких вложенных циклов?
Возврат.Работает ли `Прервать` в цикле `Пока` и `Для Каждого`?
Прервать и Продолжить действуют одинаково во всех трёх видах циклов: Для … По, Для Каждого … Из и Пока.Можно ли обойтись без цикла при подсчёте итогов?
СГРУППИРОВАТЬ ПО и ИТОГИ. Это быстрее и короче, чем накапливать суммы в переменной внутри перебора.Остановит ли `ОбработкаПрерыванияПользователя()` фоновое задание?
Отменить() снаружи или проверкой флага остановки изнутри.