В практике разработчика 1С иногда возникает крайне загадочный и трудноуловимый феномен: программа «намертво» зависает при выполнении повседневных операций (например, при подборе номенклатуры или заполнении табличной части), но стоит запустить этот же процесс в режиме пошаговой отладки в Конфигураторе — и всё начинает работать быстро, корректно и без малейших сбоев. Как только отладка завершается или точки останова отключаются, зависание возвращается.
В этой статье мы подробно проанализируем физику этого процесса, выясним скрытые причины такого поведения платформы и разберем пошаговые методики устранения подобных зависаний.
Одной из наиболее частых причин зависания системы при заполнении табличных частей является бесконечная рекурсия обработчиков событий формы. Рассмотрим типичную ситуацию: при интерактивном или программном изменении данных в строке табличной части вызывается обработчик ПриИзменении. Если внутри этого обработчика происходит модификация других колонок или строк, это может снова спровоцировать вызов события ПриИзменении, создавая бесконечный цикл обработки сообщений операционной системы.
Почему проблема исчезает при пошаговой отладке?
Когда вы устанавливаете точку останова и начинаете пошагово проходить код клавишей F11 (или F10), фокус операционной системы принудительно переключается на окно Конфигуратора. В этот момент клиентское приложение 1С теряет фокус ввода. Потеря фокуса приостанавливает генерацию многих интерфейсных событий (особенно связанных с обновлением графической формы, активизацией строк ПриАктивизацииСтроки или ячеек). Очередь событий ОС очищается или прерывается, поэтому при отладке программа успевает благополучно выйти из бесконечной цепочки вызовов.
Как решить проблему зацикливания событий?
Для предотвращения бесконечной рекурсии обработчиков событий мы рекомендуем использовать классический флаг-предохранитель. Давайте посмотрим на пример реализации такого механизма:
&НаКлиенте
Перем мБлокироватьСобытия;
&НаКлиенте
Процедура ТЧНоменклатураПриИзменении(Элемент)
Если мБлокироватьСобытия = Истина Тогда
Возврат;
КонецЕсли;
мБлокироватьСобытия = Истина;
// Выполняем программное заполнение или расчет колонок
РассчитатьСуммуСтроки(Элементы.ТЧНоменклатура.ТекущиеДанные);
мБлокироватьСобытия = Ложь;
КонецПроцедуры
Использование флага мБлокироватьСобытия гарантирует, что программное изменение данных внутри обработчика не приведет к повторному рекурсивному входу в эту же процедуру.
Еще один глубокий технический нюанс связан с передачей данных между клиентом и сервером. В клиент-серверной архитектуре при вызове серверной процедуры или функции все передаваемые параметры и возвращаемые значения проходят процесс сериализации (преобразования в транспортный XML/JSON-формат) и последующей десериализации на принимающей стороне.
Если серверная функция возвращает на клиент сложную нетипизированную коллекцию (например, массив структур или соответствий), внутри которой образовалась циклическая ссылка (когда Структура А ссылается на Структуру Б, а та, в свою очередь, содержит ссылку на Структуру А), механизм сериализатора платформы уходит в бесконечную рекурсию при попытке обойти дерево объектов.
Почему в отладке работает?
При пошаговом прохождении отладчик считывает свойства объектов «по требованию» (ленивая инициализация данных). Кроме того, отладочные паузы и особенности работы отладочного протокола передачи метаданных позволяют платформе иначе обрабатывать стек вызовов, из-за чего критического переполнения стека в процессе сериализации не происходит. При непрерывном же запуске серверный поток мгновенно утилизирует 100% ядра процессора и зависает в момент достижения оператора КонецПроцедуры или КонецФункции при возврате управления на клиент.
Как решить проблему сериализации?
Рассмотрим правильный подход к передаче сложных структур через Временное хранилище. Вместо того чтобы передавать тяжелые массивы структур напрямую через параметры серверного вызова, запишем их во временное хранилище на сервере и вернем лишь легкую строку-адрес:
&НаСервереБезКонтекста
Функция ПодготовитьДанныеДляКлиентаНаСервере()
ТаблицаДанных = Новый ТаблицаЗначений;
// ... Заполняем таблицу сложными данными
// Вместо возврата таблицы значений или массива структур,
// помещаем объект во временное хранилище
АдресХранилища = ПоместитьВоВременноеХранилище(ТаблицаДанных, Новый УникальныйИдентификатор);
Возврат АдресХранилища;
КонецФункции
&НаКлиенте
Процедура ЗаполнитьНоменклатуру()
// Вызываем серверный метод, который возвращает только адрес (строку)
АдресДанных = ПодготовитьДанныеДляКлиентаНаСервере();
// Получаем данные на клиенте из хранилища (если это необходимо)
// или передаем этот адрес в другие серверные процедуры
ОбработатьПолученныеДанные(АдресДанных);
КонецПроцедуры
Использование механизма ПоместитьВоВременноеХранилище исключает прямую избыточную сериализацию сложных иерархических структур данных между контекстами клиента и сервера, предотвращая зависание платформы.
Как показывает практика разбора подобных инцидентов на крупных внедрениях, зависания при возврате с сервера на клиент особенно часто проявляются при использовании Толстого клиента в управляемом режиме.
Толстый клиент 1С работает напрямую с системными дескрипторами окон Windows и очередью сообщений операционной системы в рамках единого процесса. При возврате управления с сервера на клиент после длительной транзакции или тяжелого запроса может произойти «потеря фокуса» родительской формы. Окно подбора уходит под основное рабочее окно, блокируя при этом весь ввод пользователя. Система выглядит полностью зависшей, хотя на самом деле она ожидает реакции на невидимое модальное окно.
Тонкий клиент спроектирован иначе: он полностью изолирован от прямого низкоуровневого манипулирования оконными дескрипторами Windows, а обмен данными жестко регламентирован и асинхронен. Именно поэтому перевод пользователей на Тонкий клиент в большинстве случаев мгновенно решает проблему зависаний при переходе контекста «Сервер -> Клиент».
Если на тестовой базе (где конфигурация обновляется монопольно) проблема отсутствует, а на рабочей базе пользователи регулярно сталкиваются с зависаниями, причиной почти наверняка является повреждение или рассогласование кэша метаданных из-за применения «динамического обновления» (обновления конфигурации базы данных без выгона пользователей).
Локальный кэш конфигурации на рабочих станциях начинает расходиться с реальной структурой таблиц на сервере. При выполнении сложных запросов в процедурах подбора клиент обращается к устаревшим метаданным, что приводит к скрытой циклической ошибке десериализации на стороне клиента.
В режиме отладки (при запуске сеанса из Конфигуратора) платформа принудительно инициализирует и проверяет актуальность метаданных, обходя поврежденные области локального кэша, из-за чего отладка проходит успешно.
Рекомендации по очистке кэша:
snccntx в папке службы сервера srvinfo)./ClearCache или физически удалить папки по путям:
%userprofile%\AppData\Local\1C\1cv8%userprofile%\AppData\Roaming\1C\1cv8Если локализовать зависший участок кода стандартными методами не удается, выполните следующие шаги:
1cv8c.exe или 1cv8.exe).Для глубокого анализа причин зависания на сервере настройте сбор Технологического журнала. Создайте в каталоге C:\Program Files\1cv8\conf файл logcfg.xml следующего содержания:
<config xmlns="http://v8.1c.ru/v8/tech-log">
<log location="C:\v8_logs" history="24">
<event filter="EXCP">
<properties>
<allproperties/>
</properties>
</event>
<event filter="SDBL">
<properties>
<allproperties/>
</properties>
</event>
</log>
</config>
При зависании сеанса проанализируйте файлы технологического журнала. Событие EXCP (исключительные ситуации) покажет скрытые системные ошибки платформы, которые происходили в момент зависания сеанса при передаче контекста на клиентскую сторону.
Своевременный переход на использование Тонкого клиента, отказ от передачи нетипизированных циклических структур через границу «Клиент-Сервер» и грамотное управление обработчиками событий формы позволят вам навсегда избавиться от подобных зависаний в вашей системе.