В процессе разработки на платформе 1С:Предприятие 8 (особенно при работе с управляемыми формами и торговым оборудованием) программисты часто сталкиваются с загадочным поведением: обработчик события ОбработкаОповещения в форме вызывается несколько раз подряд с абсолютно одинаковыми параметрами. При этом замер производительности и стек вызовов показывают, что код инициации события отрабатывает единожды, а в промежутках между вызовами никакой посторонний код не выполняется.
Давайте подробно разберем эту проблему, выясним внутренние механизмы работы платформы 1С, которые приводят к подобному поведению, и рассмотрим практические методы устранения этой ошибки.
Обычно ситуация развивается по следующему сценарию. У нас есть вспомогательная форма (например, форма ручного ввода штрихкода). После ввода данных и закрытия этой формы вызывается callback-процедура (описание оповещения):
&НаКлиенте
Процедура ПослеЗакрытияФормыРучнойВводШтрихкода (Результат, Параметры) Экспорт
Если ЗначениеЗаполнено(Результат) Тогда
// Отправка глобального оповещения всем открытым формам
Оповестить("Barcode", Результат, "ПодключаемоеОборудование");
КонецЕсли;
КонецПроцедуры
В основной форме документа или рабочего места кассира настроен стандартный приемник этого оповещения:
&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
Если Источник = "ПодключаемоеОборудование"
И (РежимОтладки = Истина Или (ВводДоступен() И РежимОтладки = Ложь)) Тогда
Если ИмяСобытия = "Barcode" Тогда
// Основная логика обработки штрихкода
ОбработатьQRКодНаКлиенте(СокрЛП(Параметр));
КонецЕсли;
КонецЕсли;
КонецПроцедуры
При тестировании мы замечаем, что процедура ОбработкаОповещения выполняется дважды или даже трижды для одного и того же штрихкода. Это приводит к дублированию строк в документе, повторным запросам к базе данных или ошибкам транзакций.
---
Это самая распространенная и неочевидная причина дублирования вызовов. Метод глобального контекста Оповестить() рассылает уведомление абсолютно всем существующим в памяти экземплярам форм, которые содержат процедуру ОбработкаОповещения.
Рассмотрим, как это происходит по шагам:
ПолучитьОкна() больше не возвращает окно этой закрытой формы.Оповестить(), платформа вызывает обработчик ОбработкаОповещения в обоих экземплярах формы. В отладчике это выглядит так, будто один и тот же код в одной форме выполнился дважды подряд.Как выполнить диагностику?
Чтобы подтвердить или опровергнуть эту гипотезу, добавим в начало обработчика оповещения вывод уникального идентификатора текущей формы в окно сообщений:
Сообщить("Сработало оповещение в форме. GUID: " + ЭтаФорма.УникальныйИдентификатор);
Если при однократном сканировании штрихкода в окно сообщений вывелось две строки с разными GUID — значит, в памяти действительно завис «фантомный» экземпляр ранее закрытой формы. Для исключения подобных ошибок интерфейса и человеческого фактора при вводе данных можно настроить роботов One RPA для автоматического переноса информации между окнами и формами.
Как решить эту проблему?
Для предотвращения выполнения кода в неактивных или зависших формах необходимо использовать проверку фокуса ввода с помощью метода ВводДоступен(). Обратите внимание на условие автора из примера:
И (РежимОтладки = Истина Или (ВводДоступен() И РежимОтладки = Ложь))
В режиме отладки переменная РежимОтладки равна Истина, что полностью нивелирует проверку ВводДоступен(). Платформа пропускает вызов в неактивной форме, и мы получаем двойное срабатывание. Замените логику работы и всегда проверяйте доступность ввода, исключая поблажки для режима отладки:
&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
// Если окно неактивно, сразу прекращаем обработку
Если Не ВводДоступен() Тогда
Возврат;
КонецЕсли;
// Дальнейшая обработка события...
КонецПроцедуры
---
Проанализируем процедуру ПослеЗакрытияФормыРучнойВводШтрихкода. Данный callback вызывается в момент закрытия вспомогательного окна ввода. Если пользователь закрывает форму ввода штрихкода нажатием на кнопку «ОК» (или клавишей Enter), событие закрытия может инициироваться дважды.
Это происходит из-за физического дребезга клавиш, двойного клика мышкой по кнопке закрытия или из-за особенностей работы аппаратных сканеров штрихкода, которые посылают символ возврата каретки (Enter) сразу после передачи текста штрихкода.
В результате процедура закрытия вспомогательной формы отрабатывает два раза подряд, генерируя два вызова callback-процедуры, каждая из которых посылает свое глобальное оповещение Оповестить().
Как выполнить диагностику?
Установим точку останова непосредственно внутри процедуры ПослеЗакрытияФормыРучнойВводШтрихкода на строке с проверкой Если ЗначениеЗаполнено(Результат) Тогда. Если при одном сканировании отладчик останавливает выполнение на этой точке два раза — проблема кроется именно во множественном вызове закрытия вспомогательного окна.
Как решить эту проблему?
В форме ввода штрихкода необходимо установить блокировку повторного нажатия кнопки отправки данных. Можно использовать простейший флаг на клиенте:
&НаКлиенте
Перем ФормаОтправлена;
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ФормаОтправлена = Ложь;
КонецПроцедуры
&НаКлиенте
Процедура КнопкаОКНажатие(Кнопка)
Если ФормаОтправлена Тогда
Возврат;
КонецЕсли;
ФормаОтправлена = Истина;
Закрыть(ВведенныйШтрихкод);
КонецПроцедуры
---
Если конфигурация является самописной или существенно доработанной, разработчики часто используют механизм расширений для кастомизации логики. Глобальный поиск по конфигурации по умолчанию может игнорировать модули активных расширений, если в настройках поиска не установлены соответствующие флаги. При работе с большим количеством доработок и расширений в разных базах можно автоматизировать архивирование, обновление и тестирование нескольких информационных баз в тихом режиме.
В расширении может быть объявлена аннотация к процедуре формы, например:
&После("ОбработкаОповещения") или &ИзменениеИКонтроль("ОбработкаОповещения")
Также возможно, что в расширении динамически подключается дополнительный обработчик ожидания или оповещения через метод ПодключитьОбработчикОповещения(), о существовании которого в основной конфигурации даже не подозревают.
Как выполнить диагностику?
Вызовем диалоговое окно глобального поиска (сочетание клавиш Ctrl + Shift + F) и обязательно убедимся, что в области поиска установлена галочка «Расширения конфигурации». Выполним поиск по ключевым словам ОбработкаОповещения и ПодключитьОбработчикОповещения.
---
Если для ввода штрихкодов используется реальное физическое оборудование, работающее в режиме эмуляции клавиатуры (Keyboard Wedge), оно передает данные так, будто их быстро ввели с клавиатуры.
Большинство сканеров по умолчанию настроены на отправку суффикса Enter (код клавиши 13) или Tab (код клавиши 9) после завершения чтения штрихкода. Если на форме документа фокус ввода находится на элементе, который реагирует на нажатие этих клавиш, может произойти конфликт интерфейсного и программного событий. Программа считывает штрихкод через обработчик драйвера оборудования и одновременно перехватывает нажатие Enter, запуская повторную обработку ввода.
Как решить эту проблему?
Переведите сканер штрихкодов из режима эмуляции клавиатуры в режим эмуляции COM-порта (через установку виртуального драйвера USB-to-serial). Это позволит 1С работать с оборудованием напрямую через специализированный драйвер («1С:Управление торговым оборудованием»), полностью исключая ложные срабатывания клавиатурных нажатий на форме.
---
Чтобы раз и навсегда обезопасить свои конфигурации от двойного срабатывания обработчиков оповещений, рекомендуем придерживаться следующих правил:
ВводДоступен() в теле обработчика ОбработкаОповещения для отсечения «фантомных» окон, оставшихся в памяти клиента.
&НаКлиенте
Перем ПоследнийШтрихкод;
&НаКлиенте
Перем ВремяПоследнейОбработки;
&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
Если ИмяСобытия = "Barcode" Тогда
ТекущееВремя = ТекущаяУниверсальнаяДатаВМиллисекундах();
Если Параметр = ПоследнийШтрихкод
И (ТекущееВремя - ВремяПоследнейОбработки) < 1000 Тогда
// Игнорируем дублирующее событие в течение 1 секунды
Возврат;
КонецЕсли;
ПоследнийШтрихкод = Параметр;
ВремяПоследнейОбработки = ТекущееВремя;
// Переход к обработке данных...
ОбработатьQRКодНаКлиенте(СокрЛП(Параметр));
КонецЕсли;
КонецПроцедуры
Применяя эти несложные архитектурные паттерны, вы сделаете интерфейс вашего прикладного решения отзывчивым и надежным, предотвратив любые неожиданности со стороны платформенного механизма оповещений. Если же требуется наладить надежный обмен данными без внесения изменений в код и создания сложных обработчиков, можно использовать программу МС:Автообмен для автоматизации регулярной синхронизации баз.