Код 1С ушёл в бесконечный цикл: как остановить его и найти причину

· 9 мин

Содержание

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

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

Операторы выхода из цикла и условия во встроенном языке

Встроенный язык 1С даёт три штатных способа покинуть блок кода. Путать их нельзя: каждый работает на своём уровне.

ОператорГде работаетЧто делает
Прерватьтолько внутри Для, Для Каждого, ПокаВыходит из ближайшего цикла, выполнение идёт после КонецЦикла
Продолжитьтолько внутри циклаПропускает остаток тела и переходит к следующей итерации
Возвратв процедуре или функцииВыходит из всей процедуры; в функции — с возвратом значения
ВызватьИсключениегде угодноПрерывает выполнение с ошибкой, ловится в Попытка ... Исключение

Отдельного оператора «выйти из Если» нет. Блок Если ... КонецЕсли либо выполняется целиком, либо пропускается. Если нужно остановить обработку при неверном условии, используйте Возврат в начале процедуры:

Процедура ПроверитьДокумент(Документ)

    Если Не ЗначениеЗаполнено(Документ.Контрагент) Тогда
        Сообщить("Не заполнен контрагент");
        Возврат;
    КонецЕсли;

    // дальше код выполняется, только если контрагент заполнен

КонецПроцедуры

Для цикла с поиском первого подходящего значения — Прервать:

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

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

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

Как прервать зависшее выполнение кода в 1С:Предприятии

Если программа уже «думает» и не отвечает, действуйте от мягкого способа к жёсткому.

Шаг 1. Ctrl+Break

  1. Переключитесь в окно 1С.
  2. Нажмите Ctrl+Break (на ноутбуках без Break часто работает Ctrl+Fn+B или Ctrl+Pause — зависит от клавиатуры).
  3. Если код на клиенте вызывает ОбработкаПрерыванияПользователя(), программа спросит, прервать ли выполнение. Ответьте «Да».

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

Шаг 2. Подождать и оценить

Долгая обработка — не всегда зависание. Перепроведение документов за год в крупной базе может идти часами. Признаки настоящего зависания:

  • 🔄 индикатор прогресса (если он есть) не двигается больше 10–15 минут;
  • 📈 процесс 1cv8c.exe или rphost.exe занимает ядро процессора на 100% без изменений;
  • 💾 растёт потребление памяти процессом — частый признак цикла, который копит данные в таблицу значений или массив.

Шаг 3. Закрыть процесс клиента

Если окно не отвечает, а данные не жалко потерять:

  1. Откройте Диспетчер задач (Ctrl+Shift+Esc).
  2. Найдите 1cv8c.exe (тонкий клиент) или 1cv8.exe (толстый клиент, Конфигуратор).
  3. Выберите «Снять задачу».

Незавершённая транзакция при этом откатывается — всё, что программа успела изменить внутри неё, не сохранится. В клиент-серверном варианте закрытие клиента не всегда останавливает серверный код сразу: сеанс может ещё какое-то время висеть в кластере.

Если интерфейс заблокирован полностью

Бывает, что зависает не одно окно, а вся работа: другие пользователи не могут провести документы, база «встала».

Файловая база. Закройте зависший клиент через Диспетчер задач на том компьютере, где он запущен. Если процесс работает у другого пользователя — попросите его или подключитесь к его сеансу. Файл 1Cv8.1CL в каталоге базы удаляйте только тогда, когда точно никто не работает , иначе можно повредить данные у тех, кто ещё подключён.

Клиент-серверная база . Сеанс завершают на сервере:

  1. Откройте консоль администрирования серверов 1С.
  2. Разверните кластер → Информационные базы → нужная база → Сеансы.
  3. Найдите сеанс зависшего пользователя. Ориентируйтесь на колонки с текущим временем вызова и временем в СУБД — у зависшего сеанса они большие и растут.
  4. В контекстном меню выберите «Удалить».

То же самое умеет утилита rac: rac session list показывает сеансы, а точный синтаксис завершения смотрите в rac help session.

Удаление сеанса обрывает работу пользователя без сохранения. Предупредите его заранее. Перезапуск службы агента сервера 1С оборвёт сеансы всех пользователей — это крайняя мера, на время, когда в базе никого нет.

Почему код зависает в цикле или условии

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

Сч = 0;
Пока Сч < 10 Цикл
    Если ПропуститьСтроку(Сч) Тогда
        Продолжить; // Сч не увеличился — цикл вечный
    КонецЕсли;
    Сч = Сч + 1;
КонецЦикла;

Исправление — увеличивать счётчик в начале тела:

Сч = 0;
Пока Сч < 10 Цикл
    Сч = Сч + 1;
    Если ПропуститьСтроку(Сч) Тогда
        Продолжить;
    КонецЕсли;
    // обработка
КонецЦикла;

Другие частые сценарии:

  • 🔁 Изменение коллекции во время обхода. Цикл добавляет строки в ту же таблицу, по которой идёт, и конец не наступает никогда.
  • ♻️ Рекурсия без выхода. Например, в обработчике ПриЗаписи объекта вызывается Записать() того же объекта. Каждая запись запускает новую, пока сеанс не упадёт с ошибкой нехватки памяти или стека.
  • ⏱ Обработчик ожидания. ПодключитьОбработчикОжидания в обработчике, который сам же этот обработчик подключает снова, без отключения — форма постоянно занята.
  • 🗄 Тяжёлый запрос. Соединение таблиц без условий даёт миллионы строк. Код не зациклен, он ждёт СУБД.
  • 🔒 Ожидание блокировки. Транзакция ждёт данные, которые держит другой сеанс. Подробнее — в разделе про сервер.
  • 🌐 Внешний ресурс. HTTP-запрос или COM-соединение без таймаута ждёт ответа, который не придёт.

Таблица симптомов и вероятных причин

Ориентир, чтобы быстро понять, куда смотреть:

СимптомВероятная причинаЧто проверить
Процессор 100%, память не растётЦикл с неизменным условиемСчётчик и условие Пока, место Продолжить
Процессор 100%, память растётЦикл пополняет коллекциюДобавление строк в обходимую таблицу или массив
Процессор почти не загружен, клиент ждётОжидание блокировки или внешнего ресурсаСеансы и блокировки в консоли кластера, таймауты соединений
Высокая нагрузка на сервер СУБДТяжёлый запросТекст запроса, условия соединений, отборы
Через ~20 секунд ошибка о времени ожидания блокировкиДругой сеанс держит данные в транзакцииКто держит блокировку, длительность его транзакции
Клиент закрывается аварийно после долгой работыБесконечная рекурсияСтек вызовов: одна и та же процедура много раз подряд
Диагностика: выберите симптом

Отладчик: как найти строку, где код застрял

Самый быстрый путь к причине — поймать зависание под отладчиком.

Остановка на текущей строке

  1. Запустите 1С:Предприятие из Конфигуратора командой Отладка → Начать отладку (F5).
  2. Воспроизведите действие, после которого программа зависает.
  3. Когда зависание началось, переключитесь в Конфигуратор и выберите Отладка → Прервать.
  4. Отладчик остановится на строке, которая выполняется прямо сейчас. Откройте стек вызовов — он покажет всю цепочку процедур.

Для клиент-серверной базы сервер должен быть запущен с разрешённой отладкой (служба агента с ключом -debug). Без этого серверный код в отладчике не остановится.

Пошаговое выполнение и табло

Встав в цикл, пройдите несколько итераций клавишами F10 (шаг через) и F11 (шаг в процедуру). Добавьте на табло переменную-счётчик и условие выхода. Если от итерации к итерации значения не меняются — причина найдена.

Точка останова с условием

Когда цикл зависает не сразу, а, скажем, на 50 000-й строке, обычная точка останова бесполезна. Поставьте точку останова с условием: например, Сч > 49990. Отладчик остановится только тогда, когда условие станет истинным.

💡 Если отладчик раз за разом останавливается на одной и той же строке и «не выходит из условия», проверьте список точек останова (Отладка → Список точек останова) — возможно, там осталась забытая точка с условием. Отключите её или удалите.

Замер производительности

Если код не зациклен, а просто медленный, включите Отладка → Замер производительности и выполните операцию. В результатах видно, какие строки выполнялись чаще всего и сколько времени заняли. Строка с сотнями тысяч вызовов — кандидат на проблему.

Серверные блокировки: когда код ждёт чужую транзакцию

В клиент-серверной базе код часто «висит» не из-за ошибки в цикле, а потому что ждёт данные, занятые другим сеансом.

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

Что делать:

  1. В консоли кластера найдите сеанс, который давно выполняет вызов и держит транзакцию.
  2. Выясните у пользователя, что он запустил. Часто это закрытие месяца, перепроведение или групповая обработка.
  3. Если операцию можно повторить позже — завершите сеанс (с предупреждением). Его транзакция откатится, блокировки снимутся.
  4. Для систематического разбора включите технологический журнал (файл logcfg.xml) с событиями TLOCK, TTIMEOUT и TDEADLOCK. Он покажет, какой сеанс, какие данные и сколько времени держал.

Технологический журнал на нагруженном сервере быстро растёт. Включайте его на ограниченное время и с отбором только нужных событий.

Как не допустить зависаний: профилактика в коде

Несколько привычек, которые снимают большую часть проблем.

  • ✅ Страховочный счётчик в Пока. Если число итераций заранее неизвестно, ограничьте его разумным максимумом.
  • ✅ Проверка прерывания в клиентских циклах. Вызов ОбработкаПрерыванияПользователя() позволяет пользователю остановить обработку по Ctrl+Break.
  • ✅ Не меняйте коллекцию, которую обходите. Складывайте новые строки во временную таблицу и добавляйте после цикла. Удаляйте строки обратным циклом по индексу.
  • ✅ Короткие транзакции. Не выполняйте внутри транзакции долгие расчёты, диалоги и обращения к внешним сервисам.
  • ✅ Запросы вместо обращений через точку в цикле. Получайте данные одним запросом до цикла.
  • ✅ Таймауты для внешних соединений. Задавайте таймаут в HTTPСоединение и аналогичных объектах.

Пример страховочного счётчика:

МаксИтераций = 100000;
Итерация = 0;
Пока Не Готово Цикл
    Итерация = Итерация + 1;
    Если Итерация > МаксИтераций Тогда
        ВызватьИсключение "Превышено допустимое число итераций";
    КонецЕсли;
    // основная обработка, которая устанавливает Готово = Истина
КонецЦикла;

Удаление строк обратным циклом — без пересчёта границы от изменяемой таблицы:

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

Индексы идут от Всего - 1 до 0, и удаление строки не сдвигает те, что ещё не обработаны.

Перед запуском обработки в рабочей базе
Выполнено: 0 / 5

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

Можно ли выйти сразу из двух вложенных циклов одним оператором?
Нет. Прервать выходит только из ближайшего цикла. Используйте флаг, который проверяется во внешнем цикле, или вынесите циклы в функцию и выйдите через Возврат.
Чем `Возврат` отличается от `Прервать`?
Прервать завершает только цикл, после него выполняется код процедуры дальше. Возврат завершает всю процедуру или функцию.
Почему Ctrl+Break не останавливает обработку?
Код выполняется на сервере или клиентский цикл не вызывает ОбработкаПрерыванияПользователя(). Тогда остаются отладчик, завершение сеанса в консоли кластера или закрытие процесса клиента.
Сохранятся ли данные, если завершить зависший сеанс?
Всё, что было записано внутри незавершённой транзакции, откатится. Документы, записанные до неё отдельными транзакциями, останутся в базе.
Как понять, зависла программа или просто долго работает?
Посмотрите, двигается ли индикатор и меняются ли счётчики в консоли кластера. Если процесс долго грузит процессор без прогресса или память неуклонно растёт — это зацикливание.