Механизм ОписаниеОповещения является краеугольным камнем для реализации немодальных операций в 1С:Предприятии 8.3, особенно в управляемых формах. Он позволяет нам взаимодействовать с пользователем через функции типа ПоказатьВопрос, ПоказатьВводЗначения, не блокируя при этом работу клиентского приложения. Однако, как показывает практика и многократные обсуждения на форумах, работа этого механизма иногда может быть нестабильной, приводя к непредсказуемым ошибкам или даже аварийному завершению работы платформы. Рассмотрим подробнее типичные сценарии возникновения проблем и выясним, как их можно эффективно решить.
Наш читатель столкнулся с ситуацией, когда код, использующий ОписаниеОповещения, ведет себя непредсказуемо. В одних случаях он работает корректно, в других — вызывает аварийное завершение платформы. Проанализируем исходный код, который демонстрирует проблему.
Исходный код, демонстрирующий проблему:
&НаКлиенте
Процедура ОбработкаКоманды(ПараметрКоманды, ПараметрыВыполненияКоманды)
СписокДействийДляВыбораПользователя = Новый СписокЗначений;
СписокДействийДляВыбораПользователя.Добавить("Посмотреть", "Посмотреть имя шаблона");
СписокДействийДляВыбораПользователя.Добавить("Создать", "Создать шаблон");
СписокДействийДляВыбораПользователя.Добавить("Переименовать", "Переименовать шаблон");
СписокДействийДляВыбораПользователя.Добавить("Удалить", "Удалить Шаблон");
ДопПараметры = Новый Структура("Ссылка, ИмяРеквизита, СписокДействийДляВыбораПользователя");
ДопПараметры.Ссылка = ПараметрыВыполненияКоманды.Источник.Объект.Ссылка;
ДопПараметры.ИмяРеквизита = ПараметрыВыполненияКоманды.Источник.ТекущийЭлемент.Имя;
ДопПараметры.СписокДействийДляВыбораПользователя = СписокДействийДляВыбораПользователя;
Описание = Новый ОписаниеОповещения("ОбработкаВыбораПользователя", ЭтотОбъект, ДопПараметры);
ПоказатьВопрос(Описание, "Что вы хотите сделать?", СписокДействийДляВыбораПользователя);
КонецПроцедуры
&НаКлиенте
Процедура ОбработкаВыбораПользователя (ДанныеВыбора, ПолученныеДопПараметры)
Если ДанныеВыбора = "Посмотреть" Тогда
ПоказатьЗначение(, ОбщийСервер.НайденныйШаблон (ПолученныеДопПараметры));
ИначеЕсли ДанныеВыбора = "Создать" Тогда
ИмяШаблона = ОбщийСервер.НайденныйШаблон (ПолученныеДопПараметры);
Если ИмяШаблона = Неопределено Тогда
Описание = Новый ОписаниеОповещения("СоздатьШаблон", ЭтотОбъект, ПолученныеДопПараметры);
ПоказатьВводЗначения(Описание,,"Введите имя шаблона", Тип("Строка"));
Иначе
Описание = Новый ОписаниеОповещения("ОбработкаВыбораПользователя", ЭтотОбъект, ПолученныеДопПараметры);
ПоказатьВопрос(Описание, " Создать невозможно! Шаблон для данного реквизита задан! Имя шаблона: ["+ ИмяШаблона +"]", ПолученныеДопПараметры.СписокДействийДляВыбораПользователя);
КонецЕсли;
ИначеЕсли ДанныеВыбора = "Переименовать" Тогда
ИмяШаблона = ОбщийСервер.НайденныйШаблон (ПолученныеДопПараметры);
Если ИмяШаблона = Неопределено Тогда
Описание = Новый ОписаниеОповещения("ОбработкаВыбораПользователя", ЭтотОбъект, ПолученныеДопПараметры);
ПоказатьВопрос(Описание, "Переименовать невозможно! Шаблон для данного реквизита не задан!", ПолученныеДопПараметры.СписокДействийДляВыбораПользователя);
Иначе
Описание = Новый ОписаниеОповещения("ПереименоватьШаблон", ЭтотОбъект, ПолученныеДопПараметры);
ПоказатьВводСтроки(Описание, ИмяШаблона,"Введите имя шаблона");
КонецЕсли;
ИначеЕсли ДанныеВыбора = "Удалить" Тогда
// ...
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура СоздатьШаблон (ВведенныеДанные, ДополнительныеПараметры)
ПоказатьЗначение(, ОбщийСервер.СоздатьШаблон(ВведенныеДанные, ДополнительныеПараметры));
КонецПроцедуры
&НаКлиенте
Процедура ПереименоватьШаблон (ВведенныеДанные, ДополнительныеПараметры)
// ...
КонецПроцедуры
Список наблюдаемых аномалий:
ПоказатьВопрос), и при этом выборе может произойти аварийное завершение с сообщением "Подождите пожалуйста идет сохранение информации об ошибке...". Это указывает на проблему с рекурсивными или цепочными вызовами интерактивных функций.Экспорт: Изначально возникла путаница с тем, должна ли процедура, указанная в ОписаниеОповещения, быть экспортной. В некоторых случаях код работал без слова Экспорт, в других — требовал его, или не работал даже с ним.НаСервере vs НаКлиенте: Если процедура, указанная в ОписаниеОповещения, объявлена с директивой &НаСервере, при обращении к ней возникает ошибка времени выполнения, заявляющая, что метод не найден. Система ожидает, что процедуры-обработчики оповещений будут выполняться на клиенте.Для того чтобы решить проблему, нам необходимо глубоко понять, как работает ОписаниеОповещения и какие требования платформа предъявляет к процедурам-обработчикам. Разберем основные принципы:
Обязательное требование Экспорт для вызываемой процедуры:
Согласно документации 1С, процедура, имя которой указывается в первом параметре конструктора ОписаниеОповещения, обязательно должна быть экспортной. Это ключевое слово указывает платформе, что данная процедура доступна для вызова извне модуля, в котором она объявлена, в том числе и через механизм оповещений. Если процедура не объявлена с Экспорт, платформа не сможет ее найти и вызовет ошибку "Не найден экспортируемый метод".
Посмотрим на пример, где процедура-обработчик является экспортной:
&НаКлиенте
Процедура ЗаполнитьПоляИсточника(Команда)
ТекущийИсточник = Элементы.СписокИсточников.ТекущиеДанные;
Если ТекущийИсточник = Неопределено Тогда
Возврат
КонецЕсли;
Если ПоляИсточника.Количество > 0 Тогда
Режим = РежимДиалогаВопрос.ДаНет;
Оповещение = Новый ОписаниеОповещения("ПослеЗакрытияВопросаЗаполнитьПоляИсточника", ЭтаФорма, Параметры);
ПоказатьВопрос(Оповещение,"Таблица полей будет очищена. Продолжить?", Режим, 0);
Иначе
ТупоЗаполнитьПоляИсточника;
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ПослеЗакрытияВопросаЗаполнитьПоляИсточника(Результат, Параметры) Экспорт
Если Результат = КодВозвратаДиалога.Нет тогда
Возврат;
КонецЕсли;
ПоляИсточника.Очистить;
ТупоЗаполнитьПоляИсточника;
КонецПроцедуры
Здесь процедура ПослеЗакрытияВопросаЗаполнитьПоляИсточника объявлена с Экспорт, что обеспечивает ее корректный вызов после закрытия вопроса.
Контекст вызова процедуры (ЭтаФорма, ЭтотОбъект, ОбщийМодуль):
Второй параметр конструктора ОписаниеОповещения указывает на модуль, в котором находится вызываемая процедура. Это может быть:
ЭтаФорма: если процедура находится в модуле управляемой формы.ЭтотОбъект: если процедура находится в модуле команды командного интерфейса.Важно точно указать, где искать процедуру, иначе она не будет найдена, даже если она экспортная.
Проблема рекурсивных/цепочных вызовов:
Основная причина аварийных завершений, которую мы наблюдаем, заключается в создании цепочки интерактивных вызовов (ПоказатьВопрос, ПоказатьВводЗначения), где один обработчик оповещения вызывает другой интерактивный диалог. Хотя механизм ОписаниеОповещения по своей природе асинхронный, многократное вложение или быстрая последовательность таких вызовов может перегружать внутренние механизмы платформы, особенно в старых версиях или при определенных режимах совместимости. Это может приводить к ошибкам в стеке вызовов или некорректной работе с контекстом, что проявляется в аварийном завершении.
Посмотрим, как модификация рабочего кода может спровоцировать проблему с цепочными вызовами:
&НаКлиенте
Процедура ПослеЗакрытияВопросаЗаполнитьПоляИсточника(Результат, Параметры) Экспорт
Если Результат = КодВозвратаДиалога.Да Тогда
Режим = РежимДиалогаВопрос.ОКОтмена;
Оповещение = Новый ОписаниеОповещения("ПослеЗакрытияВопросаЗаполнитьПоляИсточника", ЭтаФорма, Параметры);
ПоказатьВопрос(Оповещение,"Вы уверены?", Режим, 0); // Вложенный вызов ПоказатьВопрос
ИначеЕсли Результат = КодВозвратаДиалога.ОК Тогда
Описание = Новый ОписаниеОповещения("ПроцедураЕщеОдна", ЭтаФорма, Параметры);
ПоказатьВводСтроки(Описание, "Ля-ля-ля","Что хотите то введите!"); // Ещё один вложенный интерактивный вызов
КонецЕсли;
Если Результат = КодВозвратаДиалога.Нет тогда
Возврат;
КонецЕсли;
// ... остальной код
КонецПроцедуры
&НаКлиенте
Процедура ПроцедураЕщеОдна(Результат, Параметры) Экспорт
// ...
КонецПроцедуры;
В этом примере, если пользователь выбирает "Да" в первом вопросе, открывается второй вопрос ("Вы уверены?"). Если выбирается "ОК" во втором вопросе, открывается диалог ввода строки. Такая последовательность может стать причиной нестабильности.
Директивы компиляции &НаКлиенте и &НаСервере:
Процедура, которая является обработчиком оповещения и вызывается после интерактивных действий пользователя (например, закрытия диалога ПоказатьВопрос), всегда должна быть клиентской. Это логично, поскольку интерактивное взаимодействие происходит на клиентской стороне. Если такую процедуру объявить с директивой &НаСервере, платформа не сможет ее вызвать из клиентского контекста, что приведет к ошибке "Не найден экспортируемый метод". Серверные вызовы должны инициироваться из клиентского кода через соответствующие механизмы (например, вызов серверной процедуры из клиентской).
Основываясь на анализе и опыте разработчиков, мы можем предложить несколько подходов для обеспечения стабильной работы ОписаниеОповещения.
Один из наиболее эффективных способов устранить нестабильность, особенно при цепочных вызовах, — это вынести логику обработки оповещений в общий неглобальный модуль. Это решение помогло автору исходной проблемы. Почему это работает?
ОписаниеОповещения, платформа точно знает, где искать процедуру, что может быть более надежным, чем неявные связи с ЭтотОбъект или ЭтаФорма в некоторых сценариях.Как это сделать:
УправлениеШаблонамиКлиент.ОбработкаВыбораПользователя, СоздатьШаблон, ПереименоватьШаблон).&НаКлиенте и ключевым словом Экспорт.ОписаниеОповещения вместо ЭтотОбъект или ЭтаФорма укажите имя вашего общего модуля.Пример корректировки (концептуально):
В модуле формы (или команды):
&НаКлиенте
Процедура ОбработкаКоманды(ПараметрКоманды, ПараметрыВыполненияКоманды)
// ... формирование ДопПараметров
// Вызываем процедуру из общего модуля
Описание = Новый ОписаниеОповещения("УправлениеШаблонамиКлиент.ОбработкаВыбораПользователя", Неопределено, ДопПараметры);
// Обратите внимание: второй параметр теперь может быть Неопределено,
// так как мы указываем полное имя процедуры в первом параметре,
// или же передаем имя общего модуля, если процедура объявлена в нем без префикса.
// В большинстве случаев для общего модуля в качестве второго параметра указывают сам модуль
// или Неопределено, если имя процедуры полностью квалифицировано.
// Если модуль неглобальный, то он сам выступает в качестве контекста
Описание = Новый ОписаниеОповещения("ОбработкаВыбораПользователя", УправлениеШаблонамиКлиент, ДопПараметры);
ПоказатьВопрос(Описание, "Что вы хотите сделать?", СписокДействийДляВыбораПользователя);
КонецПроцедуры
В общем клиентском модуле УправлениеШаблонамиКлиент:
&НаКлиенте
Процедура ОбработкаВыбораПользователя (ДанныеВыбора, ПолученныеДопПараметры) Экспорт
// ... вся логика из исходного ОбработкаВыбораПользователя
// При создании новых оповещений, ссылаемся на ЭтотОбъект, если находимся в модуле общего модуля
// Если же процедуры в этом же модуле, то:
// Описание = Новый ОписаниеОповещения("СоздатьШаблон", ЭтотОбъект, ПолученныеДопПараметры); // ЭтотОбъект здесь будет ссылаться на сам общий модуль
// Если же в качестве Модуля передавали Неопределено, то необходимо использовать полный путь:
// Описание = Новый ОписаниеОповещения("УправлениеШаблонамиКлиент.СоздатьШаблон", Неопределено, ПолученныеДопПараметры);
// Или, как выше, передавать объект модуля:
Описание = Новый ОписаниеОповещения("СоздатьШаблон", УправлениеШаблонамиКлиент, ПолученныеДопПараметры);
ПоказатьВводЗначения(Описание,,"Введите имя шаблона", Тип("Строка"));
КонецПроцедуры
&НаКлиенте
Процедура СоздатьШаблон (ВведенныеДанные, ДополнительныеПараметры) Экспорт
// ...
КонецПроцедуры
&НаКлиенте
Процедура ПереименоватьШаблон (ВведенныеДанные, ДополнительныеПараметры) Экспорт
// ...
КонецПроцедуры
Для разработчиков, использующих обычные формы, проблема может быть схожей. В этом случае, если процедура оповещения находится в модуле обычного объекта (например, документа или справочника), а не в модуле формы, необходимо указать ЭтотОбъект как контекст для ОписаниеОповещения.
Пример:
// В модуле обычного объекта (например, документа)
Процедура МояКоманда(Команда)
Оповещение = Новый ОписаниеОповещения("ОбработатьВыборВМодулеОбъекта", ЭтотОбъект, ДополнительныеПараметры);
// ...
КонецПроцедуры
Процедура ОбработатьВыборВМодулеОбъекта(Результат, ДополнительныеПараметры) Экспорт
// ...
КонецПроцедуры
Здесь важно, что процедура ОбработатьВыборВМодулеОбъекта находится в модуле самого объекта, а не в модуле его формы.
Некоторые пользователи сообщают, что при обновлении платформы или переходе на режим совместимости 8.3.21 и выше, ранее работавший код с ОписаниеОповещения может перестать работать, даже если процедура объявлена с Экспорт и &НаКлиенте. Проблема может проявляться, если обработчик оповещения должен вызвать серверную процедуру.
Описание проблемы и решения:
В режимах совместимости 8.3.21+ платформа может более строго интерпретировать контексты вызовов. Если ваш обработчик оповещения был серверным или напрямую вызывал серверную функцию, это могло вызывать сбой.
Эффективное решение для таких случаев:
Создайте клиентскую экспортную процедуру-обработчик оповещения, а уже из нее вызывайте необходимую серверную процедуру.
Пример кода:
// НаКлиенте в модуле формы или общего клиентского модуля
&НаКлиенте
Процедура МояКоманда(Команда)
Оповещение = Новый ОписаниеОповещения("ПослеВыбораДействия", ЭтаФорма, ДополнительныеПараметры);
ПоказатьВопрос(Оповещение, "Что делаем?", РежимДиалогаВопрос.ДаНет);
КонецПроцедуры
// НаКлиенте в модуле формы или общего клиентского модуля
&НаКлиенте
Процедура ПослеВыбораДействия(Результат, ДополнительныеПараметры) Экспорт
Если Результат = КодВозвратаДиалога.Да Тогда
// Здесь мы вызываем серверную процедуру, но уже из клиентского экспортного обработчика
СервернаяЛогика(ДополнительныеПараметры);
КонецЕсли;
КонецПроцедуры
// НаСервере в модуле формы или в общем серверном модуле
&НаСервере
Процедура СервернаяЛогика(ДополнительныеПараметры)
// Выполняем сложные операции на сервере
Сообщить("Серверная логика выполнена.");
КонецПроцедуры
Такой подход обеспечивает четкое разделение клиентской и серверной логики и соответствует требованиям платформы в новых режимах совместимости.
Если вы столкнулись с проблемами в работе ОписаниеОповещения, рекомендуем выполнить следующие действия:
ОписаниеОповещения.ПоказатьВопрос передает Результат и ДополнительныеПараметры).ОписаниеОповещения и указывает на корректный контекст (ЭтаФорма, ЭтотОбъект или имя общего модуля).Следуя этим рекомендациям, мы сможем добиться стабильной и предсказуемой работы с механизмом ОписаниеОповещения в 1С:Предприятии 8.3.