Прерываем цикл во встроенном языке 1С 8.3: от оператора Прервать до зависшей обработки
Содержание
Чтобы принудительно выйти из цикла в 1С, напишите внутри тела оператор Прервать; — выполнение сразу перейдёт на строку после КонецЦикла. Оператор работает во всех трёх видах циклов: Для, Для Каждого и Пока.
Сложности начинаются дальше. Прервать выходит только из одного, ближайшего цикла. Меток у него нет. А зависший цикл в работающей обработке оператором уже не остановишь. Для каждого из этих случаев есть свой приём, и все они работают на платформе 8.3.
Оператор Прервать: выход из цикла по условию
Прервать завершает текущий цикл немедленно. Оставшаяся часть тела не выполняется, следующих итераций не будет.
Классический случай — поиск первого подходящего элемента:
НайденнаяСтрока = Неопределено;
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
Если СтрокаТЧ.Количество = 0 Тогда
НайденнаяСтрока = СтрокаТЧ;
Прервать;
КонецЕсли;
КонецЦикла;
Если НайденнаяСтрока <> Неопределено Тогда
Сообщить("Пустое количество в строке " + Формат(НайденнаяСтрока.НомерСтроки, "ЧГ=0"));
КонецЕсли;
То же самое в цикле со счётчиком:
Для Индекс = 0 По Массив.ВГраница() Цикл
Если Массив[Индекс] < 0 Тогда
Прервать;
КонецЕсли;
Сумма = Сумма + Массив[Индекс];
КонецЦикла;
Что нужно помнить:
- 🔹
Прерватьдопустим только внутри цикла. Вне цикла модуль не пройдёт синтаксический контроль. - 🔹 Внутри
Еслиоператор выходит из цикла, а не из условия: уЕслисвоего «выхода» нет. - 🔹 Переменная счётчика после
Прерватьсохраняет значение той итерации, на которой произошёл выход. Это удобно: по ней видно, где остановились. - 🔹 Блок
Попыткавнутри циклаПрерватьне мешает: управление уйдёт заКонецЦикла, минуяИсключение.
Если задача — просто найти строку, цикл часто не нужен. У таблицы значений и табличной части есть Найти() и НайтиСтроки(), у массива — Найти(). Код короче, а выходить из цикла вообще не придётся.
Выход из вложенных циклов: флаг, функция, Перейти
Во встроенном языке 1С нет конструкции «прервать внешний цикл по метке», как break label в Java. Прервать во внутреннем цикле вернёт управление во внешний, и тот продолжит работу.
Есть три способа выйти сразу из всех уровней.
Способ 1. Переменная-флаг
Самый понятный вариант. Внутренний цикл ставит флаг и прерывается, внешний проверяет флаг сразу после него:
Найдено = Ложь;
Для Каждого Склад Из СписокСкладов Цикл
Для Каждого Товар Из СписокТоваров Цикл
Если ОстатокОтрицательный(Склад, Товар) Тогда
Найдено = Истина;
Прервать; // выход из внутреннего цикла
КонецЕсли;
КонецЦикла;
Если Найдено Тогда
Прервать; // выход из внешнего цикла
КонецЕсли;
КонецЦикла;
На три уровня вложенности проверку флага придётся повторить на каждом уровне. Это громоздко, но читается однозначно.
Способ 2. Вынести циклы в функцию и выйти через Возврат
Возврат завершает всю процедуру или функцию, сколько бы циклов ни было открыто. Поэтому вложенный перебор удобно вынести в отдельную функцию:
Функция ПервыйОтрицательныйОстаток(СписокСкладов, СписокТоваров)
Для Каждого Склад Из СписокСкладов Цикл
Для Каждого Товар Из СписокТоваров Цикл
Если ОстатокОтрицательный(Склад, Товар) Тогда
Возврат Новый Структура("Склад, Товар", Склад, Товар);
КонецЕсли;
КонецЦикла;
КонецЦикла;
Возврат Неопределено;
КонецФункции
Код читается без флагов, а результат поиска сразу возвращается вызывающему коду. Для глубокой вложенности это лучший вариант.
Способ 3. Оператор Перейти и метка
В языке есть Перейти и метки вида ~ИмяМетки:. Переход из цикла наружу разрешён:
Для Каждого Склад Из СписокСкладов Цикл
Для Каждого Товар Из СписокТоваров Цикл
Если ОстатокОтрицательный(Склад, Товар) Тогда
Перейти ~ПослеПоиска;
КонецЕсли;
КонецЦикла;
КонецЦикла;
~ПослеПоиска: Сообщить("Проверка остатков закончена");
Стандарты разработки конфигураций 1С не рекомендуют Перейти: код с переходами труднее читать и сопровождать. Передать управление внутрь цикла, условия или блока Попытка снаружи нельзя. В типовых конфигурациях этот оператор практически не встречается — используйте флаг или функцию.
Условие выхода в заголовке цикла Пока
Часто Прервать в цикле Пока — признак того, что условие выхода стоит не на своём месте. Если остановка зависит от одного-двух условий, их лучше вписать прямо в заголовок.
Было:
Пока Истина Цикл
Если Попыток >= 5 Тогда
Прервать;
КонецЕсли;
Успех = ОтправитьФайл(ИмяФайла);
Если Успех Тогда
Прервать;
КонецЕсли;
Попыток = Попыток + 1;
КонецЦикла;
Стало:
Попыток = 0;
Успех = Ложь;
Пока Не Успех И Попыток < 5 Цикл
Успех = ОтправитьФайл(ИмяФайла);
Попыток = Попыток + 1;
КонецЦикла;
Условие видно сразу, и цикл гарантированно закончится не позже пятой попытки.
Самый частый Пока в 1С — обход результата запроса. Здесь условие выхода встроено: Следующий() возвращает Ложь, когда записи закончились.
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Если Выборка.Сумма > Лимит Тогда
ПревышениеНайдено = Истина;
Прервать;
КонецЕсли;
КонецЦикла;
Конструкция Пока Истина Цикл оправдана, когда условий выхода много и они проверяются в разных местах тела. Но тогда у цикла обязательно должен быть ограничитель — счётчик итераций или проверка времени. Иначе одна ошибка в данных превращает его в бесконечный.
Ещё одна деталь цикла Для: границы вычисляются один раз при входе. Поэтому Для Сч = 0 По Массив.ВГраница() Цикл не заметит, что массив уменьшился внутри тела, и дойдёт до несуществующего индекса. При удалении элементов идите с конца:
Всего = Массив.Количество();
Для Сч = 1 По Всего Цикл
Индекс = Всего - Сч;
Если Массив[Индекс] = 0 Тогда
Массив.Удалить(Индекс);
КонецЕсли;
КонецЦикла;
Выход из цикла через исключение
ВызватьИсключение прерывает не только цикл, но и всю цепочку вызовов — до ближайшего блока Попытка ... Исключение. Если такого блока нет, пользователь увидит сообщение об ошибке, а выполнение остановится.
Когда это оправдано:
- ❗ дальнейшая обработка бессмысленна: битые данные, нет доступа к файлу, не найден обязательный реквизит;
- ❗ цикл стоит глубоко внутри вызовов, и протаскивать флаг через пять функций хуже, чем выбросить ошибку;
- ❗ нужно откатить транзакцию целиком.
НачатьТранзакцию();
Попытка
Для Каждого Строка Из ДанныеЗагрузки Цикл
Если Не ЗначениеЗаполнено(Строка.Контрагент) Тогда
ВызватьИсключение "Не заполнен контрагент в строке " + Формат(Строка.НомерСтроки, "ЧГ=0");
КонецЕсли;
ЗаписатьДокумент(Строка);
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
Сообщить(ОписаниеОшибки());
КонецПопытки;
Когда не надо:
- 🚫 для обычного поиска «нашёл — вышел» — исключение здесь медленнее и запутывает логику;
- 🚫 если в блоке
Исключениеошибка просто «глотается» без записи: настоящий сбой станет неотличим от вашего управляемого выхода.
Если цикл работает внутри транзакции, а исключение перехвачено в Попытке, транзакцию нужно явно отменить через ОтменитьТранзакцию(). Продолжать запись после ошибки в той же транзакции платформа не даст.
Продолжить, Возврат и «Выйти»: сравнение операторов
Рядом с Прервать путают ещё несколько операторов. Сведём их в одну таблицу:
| Оператор | Что прерывает | Куда переходит управление | Когда применять |
|---|---|---|---|
Прервать | текущий (ближайший) цикл | на строку после КонецЦикла | нашли нужное, дальше перебирать незачем |
Продолжить | только текущую итерацию | в начало следующей итерации | строку надо пропустить, но цикл продолжить |
Возврат | всю процедуру или функцию | в вызывающий код | выход из вложенных циклов, ранний возврат результата |
ВызватьИсключение | цепочку вызовов | в ближайший Исключение | ошибка, при которой продолжать нельзя |
Перейти ~Метка | все циклы до метки | на помеченный оператор | почти никогда, только в старом коде |
Пример с Продолжить — пропускаем строки без суммы:
Для Каждого Строка Из ТаблицаОплат Цикл
Если Строка.Сумма = 0 Тогда
Продолжить;
КонецЕсли;
Итого = Итого + Строка.Сумма;
КонецЦикла;
В цикле Для со счётчиком Продолжить всё равно увеличит счётчик. В цикле Пока переход идёт к проверке условия — и если счётчик меняется в конце тела, Продолжить его пропустит. Так получается бесконечный цикл, поэтому в Пока увеличивайте счётчик до первого Продолжить.
Оператора Выйти во встроенном языке 1С 8.3 нет. Если в старом коде или на форуме встречается «выйти из цикла», имеется в виду Прервать. Метод ЗавершитьРаботуСистемы() закрывает сеанс 1С целиком, к циклам он отношения не имеет.
Прервать; сразу после действия с найденным элементом.Возврат. Если функцию выделить нельзя — флаг и Прервать на каждом уровне.Продолжить;. В цикле Пока изменяйте счётчик до этого оператора.ВызватьИсключение внутри Попытка, в блоке Исключение — ОтменитьТранзакцию().Как прервать зависший цикл в работающей 1С
Бывает, что цикл уже запущен и не заканчивается: обработка «висит», индикатор не движется. Тут операторы не помогут, нужно остановить выполнение извне.
Ctrl+Break в клиенте
В режиме 1С:Предприятие нажмите Ctrl+Break. Платформа спросит, прервать ли выполнение, и при подтверждении остановит код встроенного языка. Комбинация надёжно срабатывает для кода, который выполняется на клиенте. Если долгий вызов идёт на сервере, реакции может не быть до его окончания.
Чтобы длинный клиентский цикл гарантированно откликался на Ctrl+Break, вставьте в его тело вызов ОбработкаПрерыванияПользователя():
Для Каждого Файл Из СписокФайлов Цикл
ОбработкаПрерыванияПользователя();
ЗагрузитьФайл(Файл);
КонецЦикла;
Метод проверяет, нажал ли пользователь комбинацию, и прерывает выполнение после подтверждения. Работает он в клиентском коде; на сервере его не вызывают.
Отмена фонового задания
Долгие серверные циклы правильно запускать фоновым заданием. Его можно остановить, не трогая сеанс пользователя:
Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(ИдентификаторЗадания);
Если Задание <> Неопределено Тогда
Задание.Отменить();
КонецЕсли;
В БП 3.0, ЗУП 3.1 и УТ 11 активные задания видны в разделе Администрирование → Обслуживание → Регламентные операции → Регламентные и фоновые задания. Там же выделенное задание можно отменить.
Завершение сеанса или процесса
Если ничего не помогает:
- В клиент-серверной базе откройте консоль администрирования серверов 1С, найдите информационную базу, затем список её сеансов.
- Найдите сеанс пользователя с зависшей обработкой (по имени пользователя и времени начала) и завершите его.
- В файловой базе остаётся закрыть процесс
1cv8.exeили1cv8c.exeэтого пользователя в диспетчере задач.
Принудительное завершение сеанса обрывает все несохранённые действия пользователя: открытые документы и незафиксированные транзакции будут потеряны. Предупредите пользователя заранее. Завершайте только конкретный сеанс, не перезапускайте службу сервера в рабочее время — оборвутся сеансы всех пользователей.
Отладчик тоже умеет останавливать код: подключитесь к сеансу из Конфигуратора и остановите выполнение через меню Отладка. Отладчик покажет строку, на которой крутится цикл, и значения переменных. Это самый быстрый способ понять, почему он не заканчивается.
Ошибки, из-за которых цикл не останавливается
Бесконечный цикл почти всегда вырастает из одной из этих мелочей.
- 🐞 Забыли вызвать
Следующий(). Цикл по выборке, гдеСледующий()вызывается не в заголовке, а внутри тела под условием. Если условие не выполнилось, выборка стоит на месте. - 🐞 Счётчик меняется после
Продолжить. Уже упомянутая ловушка циклаПока. - 🐞
Прерватьво внутреннем цикле вместо внешнего. Код «выходит», но внешний цикл продолжает перебор — и находит второй, ненужный результат. - 🐞 Условие с плавающей точкой.
Пока Остаток <> 0 Циклпри делении сумм может не дойти до точного нуля. Округляйте черезОкр()или сравнивайте с допуском. - 🐞 Исключение «проглатывается» внутри цикла.
Попыткав теле перехватывает ошибку, а цикл крутится дальше с теми же данными. Запишите ошибку в журнал регистрации черезЗаписьЖурналаРегистрации()и решите, прерывать ли обход.
Можно ли выйти из цикла по метке, как в Java?
Прервать в 1С не принимает метку и выходит только из ближайшего цикла. Для выхода из нескольких уровней используйте флаг, вынос в функцию с Возврат или, в крайнем случае, Перейти.Что быстрее: Прервать или условие в заголовке Пока?
Прервать.