При работе в многопользовательской среде 1С:Предприятие, особенно в таких нагруженных конфигурациях, как 1С:Комплексная автоматизация 2.5, часто возникает ситуация, когда один и тот же объект (например, документ) модифицируется одновременно из разных источников. Это может быть два активных пользователя или пользователь и фоновое задание (обработка). В идеальных условиях платформа должна выдать стандартное диалоговое окно с предложением «Перечитать» данные. Однако на практике разработчики часто сталкиваются с критической ошибкой «Возникла непредвиденная ситуация», которая прерывает работу пользователя и не дает корректно обновить данные формы. В этой статье мы подробно разберем, почему это происходит и как программно обеспечить стабильное поведение системы.
Для начала проанализируем, как 1С отслеживает изменения объектов. Платформа использует механизм оптимистической блокировки, основанный на скрытом реквизите ВерсияДанных (в базе данных SQL это поле DataVersion). Рассмотрим этот процесс по шагам:
ВерсияДанных в таблице СУБД инкрементируется (изменяется).ИсключениеДанныеИзмененыДругимПользователем.В штатном режиме это исключение перехватывается самой платформой, и пользователь видит окно с кнопкой «Перечитать». Но если в процесс вмешиваются определенные факторы, механизм «ломается», и мы получаем «непредвиденную ситуацию».
Проанализируем основные факторы, которые приводят к краху вместо штатного предложения обновить данные. Рассмотрим их подробнее:
1. Особенности платформы 8.3.23 и Web-клиента. На релизах платформы ветки 8.3.23 (включая 8.3.23.1865) была замечена нестабильность при обработке исключений в управляемых формах. Если форма открыта через Web-клиент, HTTP-соединение может некорректно обрабатывать сериализацию ошибки, что приводит к выдаче общего сообщения о непредвиденной ситуации вместо специализированного диалога.
2. Серверное кэширование объектов. Платформа активно кэширует данные на стороне сервера приложений. Если документ был изменен фоновым заданием, кэш конкретного рабочего процесса может не успеть обновиться. Когда форма пытается выполнить серверный вызов, происходит конфликт между кэшированным состоянием объекта в памяти и его реальным состоянием в базе данных, что вызывает сбой десериализации формы.
3. Влияние расширений конфигурации. В современных версиях КА 2.5 активно используются расширения. Если расширение добавляет свои реквизиты к документу или содержит подписки на события, это усложняет структуру объекта. Конфликт версий в данных расширения иногда обрабатывается платформой как критическая ошибка, так как стандартный механизм сравнения версий не всегда может однозначно сопоставить изменения в основной таблице и таблицах расширений.
4. Транзакционные блокировки. Не стоит путать объектную (оптимистическую) блокировку с транзакционной. Если первый сеанс (обработка) еще не завершил транзакцию (например, из-за длительных расчетов в ПриЗаписи), а второй сеанс пытается прочитать или записать объект, может возникнуть таймаут блокировки. Платформа иногда маскирует ошибку таймаута под «непредвиденную ситуацию».
Наиболее надежный способ борьбы с этой проблемой — взять контроль версий в свои руки. Мы можем самостоятельно проверить актуальность данных перед тем, как платформа инициирует свою проверку. Рассмотрим пример кода, который можно разместить в модуле формы или в модуле объекта (в зависимости от логики вашего решения).
Проанализируем код для реализации в модуле документа или через подписку на событие ПередЗаписью:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Если объект новый, проверять версию не имеет смысла
Если ЭтоНовый() Тогда
Возврат;
КонецЕсли;
// Получаем текущую версию данных непосредственно из базы данных, минуя кэш формы
АктуальнаяВерсия = Ссылка.ВерсияДанных;
// Сравниваем версию в памяти объекта с версией в БД
Если ЭтотОбъект.ВерсияДанных <> АктуальнаяВерсия Тогда
ТекстСообщения = "Документ был изменен в другом сеансе.
|Рекомендуется перечитать данные перед выполнением операции.";
ОбщегоНазначенияКлиентСервер.СообщитьПользователю(ТекстСообщения);
// В некоторых случаях можно вызвать исключение, чтобы остановить процесс
// ВызватьИсключение "Данные объекта изменены";
Отказ = Истина;
КонецЕсли;
КонецПроцедуры
Этот метод позволяет «мягко» прервать запись до того, как платформа столкнется с критическим конфликтом версий.
Рассмотрим еще один изящный способ, который позволяет принудительно вызвать штатный механизм обновления. Перед тем как выполнять действия по перепроведению в коде формы, мы можем попытаться явно заблокировать объект. Если объект изменен, метод Заблокировать() вызовет исключение, которое мы перехватим.
Посмотрим на пример реализации в модуле формы:
&НаКлиенте
Процедура ВыполнитьПерепроведение()
Попытка
// Пытаемся заблокировать объект на сервере
ЗаблокироватьОбъектНаСервере();
// Если блокировка прошла успешно, вызываем стандартную запись
ЭтотОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
// Если возникла ошибка блокировки или конфликта версий
ТекстОшибки = ИнформацияОбОшибке().Описание;
Если Найти(ТекстОшибки, "Данные изменены другим пользователем") > 0 Тогда
// Принудительно вызываем метод формы для обновления данных
ЭтаФорма.Прочитать();
Сообщить("Данные были обновлены, так как документ изменился. Повторите попытку.");
Иначе
// Если ошибка другого рода, выводим её пользователю
ВызватьИсключение;
КонецЕсли;
КонецПопытки;
КонецПроцедуры
&НаСервере
Процедура ЗаблокироватьОбъектНаСервере()
ОбъектДокумента = РеквизитВЗначение("Объект");
ОбъектДокумента.Заблокировать();
КонецПроцедуры
Чтобы минимизировать вероятность возникновения ошибки, мы должны научить систему сообщать открытым формам о том, что данные изменились. Если ваша обработка в первом сеансе меняет документ, крайне желательно в конце транзакции вызвать метод ОповеститьОбИзменении().
Разберем, как это работает:
Платформа 1С поддерживает механизм глобальных оповещений. Когда вызывается ОповеститьОбИзменении(Ссылка), все открытые формы, отображающие данный объект, получают сигнал. Если форма настроена правильно, она либо сама предложит обновиться, либо обновит кэш, что позволит избежать «непредвиденной ситуации» при последующем нажатии кнопки «Провести».
Пример кода в обработке, которая меняет документ:
// ... код модификации документа ...
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
// Оповещаем систему, чтобы другие сеансы узнали об изменениях
ОповеститьОбИзменении(ДокументОбъект.Ссылка);
Мы проанализировали ситуацию, когда стандартный механизм 1С дает сбой при одновременном доступе к данным. Выяснилось, что ошибка «Возникла непредвиденная ситуация» чаще всего связана с рассинхронизацией серверного кэша или спецификой работы веб-клиента на платформе 8.3.23.
Для решения проблемы рекомендуем придерживаться следующей стратегии:
ОповеститьОбИзменении() в коде обработок, которые массово меняют документы.ВерсияДанных в обработчике ПередЗаписью.Соблюдение этих правил сделает работу ваших пользователей в 1С:Комплексная автоматизация гораздо стабильнее, а интерфейс — отзывчивее.