Прерываем цикл во встроенном языке 1С 8.3: от оператора Прервать до зависшей обработки

· 9 мин

Содержание

Чтобы принудительно выйти из цикла в 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. В клиент-серверной базе откройте консоль администрирования серверов 1С, найдите информационную базу, затем список её сеансов.
  2. Найдите сеанс пользователя с зависшей обработкой (по имени пользователя и времени начала) и завершите его.
  3. В файловой базе остаётся закрыть процесс 1cv8.exe или 1cv8c.exe этого пользователя в диспетчере задач.

Принудительное завершение сеанса обрывает все несохранённые действия пользователя: открытые документы и незафиксированные транзакции будут потеряны. Предупредите пользователя заранее. Завершайте только конкретный сеанс, не перезапускайте службу сервера в рабочее время — оборвутся сеансы всех пользователей.

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

Ошибки, из-за которых цикл не останавливается

Бесконечный цикл почти всегда вырастает из одной из этих мелочей.

  • 🐞 Забыли вызвать Следующий(). Цикл по выборке, где Следующий() вызывается не в заголовке, а внутри тела под условием. Если условие не выполнилось, выборка стоит на месте.
  • 🐞 Счётчик меняется после Продолжить. Уже упомянутая ловушка цикла Пока.
  • 🐞 Прервать во внутреннем цикле вместо внешнего. Код «выходит», но внешний цикл продолжает перебор — и находит второй, ненужный результат.
  • 🐞 Условие с плавающей точкой. Пока Остаток <> 0 Цикл при делении сумм может не дойти до точного нуля. Округляйте через Окр() или сравнивайте с допуском.
  • 🐞 Исключение «проглатывается» внутри цикла. Попытка в теле перехватывает ошибку, а цикл крутится дальше с теми же данными. Запишите ошибку в журнал регистрации через ЗаписьЖурналаРегистрации() и решите, прерывать ли обход.
Можно ли выйти из цикла по метке, как в Java?
Нет. Прервать в 1С не принимает метку и выходит только из ближайшего цикла. Для выхода из нескольких уровней используйте флаг, вынос в функцию с Возврат или, в крайнем случае, Перейти.
Что быстрее: Прервать или условие в заголовке Пока?
Разница в скорости несущественна. Выбирайте то, что проще читать: одно-два условия — в заголовок, много условий в разных местах тела — Прервать.
Работает ли Прервать в цикле по выборке запроса?
Да. Выборка просто перестаёт читаться, оставшиеся записи не обрабатываются. Закрывать или освобождать выборку отдельно не нужно.
Как прервать цикл в обработке, которую запустил другой пользователь?
Если она работает в фоновом задании — отмените задание. Если в сеансе пользователя — попросите его нажать Ctrl+Break или завершите его сеанс через консоль сервера, предупредив о потере несохранённых данных.
Почему Ctrl+Break не останавливает обработку?
Скорее всего, долгий код выполняется на сервере. Комбинация прерывает клиентский код. Вынесите тяжёлый цикл в фоновое задание с возможностью отмены.