Почему возникает ошибка при многократном открытии формы «Клиент-Банк» в 1С и как ее решить?

Программист 1С v8.3 (Управляемые формы) Бухгалтерский учет IT и автоматизация бизнеса
← На главную

При автоматизации рутинных операций в 1С часто возникает задача открыть сразу несколько окон одной и той же обработки для разных параметров. Например, нам необходимо открыть форму стандартной обработки «Клиент-Банк» (или «Обмен с банком») для нескольких организаций или банковских счетов одновременно, чтобы пользователь мог визуально проконтролировать процесс загрузки выписок.

Однако при попытке реализовать это простым циклом разработчики часто сталкиваются с неприятным поведением системы: открывается только первая форма, а на последующих строках кода на мгновение мелькает окно ошибки, после чего процесс прерывается, и новые окна закрываются. В этой статье мы подробно разберем, почему так происходит, какие системные механизмы платформы «1С:Предприятие» за это отвечают, и рассмотрим несколько эффективных способов обхода этой проблемы.

Проанализируем исходный код, который обычно приводит к подобной ошибке:


&НаКлиенте
Процедура КомандаЗагрузить(Команда)
    нпп = 0;
    Для каждого стр Из ТаблицаЗагрузить Цикл
        нпп = нпп + 1;
        Если стр.Надо Тогда
            ОткрытьФормуОбработкиКлиентБанкЗагрузить(стр, нпп);
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

&НаКлиенте
Процедура ОткрытьФормуОбработкиКлиентБанкЗагрузить(стр, нпп)
    СтруктураПараметров = Новый Структура;
    СтруктураПараметров.Вставить("РежимПоУмолчанию", "ГруппаЗагрузка");
    СтруктураПараметров.Вставить("Организация", стр.Организация);
    СтруктураПараметров.Вставить("БанковскийСчет", стр.БанковскийСчет);
    СтруктураПараметров.Вставить("ФайлЗагрузки", стр.Файл);
    
    // Пытаемся открыть уникальную форму, используя нпп в качестве ключа уникальности
    Форма1 = ОткрытьФорму("Обработка.КлиентБанк.Форма", СтруктураПараметров, , нпп, ВариантОткрытияОкна.ОтдельноеОкно, , , РежимОткрытияОкнаФормы.Независимый);
    Форма1.Заголовок = "Счет " + стр.БанковскийСчет;    
КонецПроцедуры

Выясняем истинную причину ошибки

На первый взгляд, код написан абсолютно верно. Разработчик позаботился об уникальности окон на клиенте, передавая во второй параметр метода ОткрытьФорму (после структуры параметров) переменную нпп (номер по порядку) в качестве ключа уникальности. Это действительно гарантирует, что на клиенте платформа попытается создать новые независимые окна, а не активизировать уже открытое.

Тем не менее, возникает ошибка. Если поймать выполнение кода в режиме отладки на паузе или включить остановку по ошибке, мы увидим системное исключение: «Задание с таким значением ключа уже выполняется». Почему оно возникает, если мы открываем формы обработок?

Дело в том, что современный «Клиент-Банк» в типовых конфигурациях (например, в «1С:Бухгалтерии 3.0») — это сложный механизм. При инициализации формы (в обработчике ПриСозданииНаСервере или при открытии на клиенте) система автоматически запускает фоновые процессы. Это может быть:

Для выполнения этих тяжелых операций конфигурация использует метод ФоновыеЗадания.Выполнить(). Чтобы избежать дублирования одинаковых фоновых процессов в рамках одного сеанса пользователя, разработчики фирмы «1С» задают для этих заданий жесткий Ключ. Обычно этот ключ формируется как комбинация имени пользователя, организации и самой операции.

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

Попытки передать параметр Ключ в СтруктураПараметров при открытии формы обработки ни к чему не приведут. Этот параметр зарезервирован платформой только для форм объектов баз данных (справочников, документов) для указания ссылки на конкретный элемент, и игнорируется обычными обработками.

---

Решение 1. Разнесение открытия окон во времени (клиентский метод)

Если нам критически важно открыть именно интерактивные формы для пользователя и конфигурация находится на поддержке (мы не можем менять код самой обработки), самым простым и надежным решением будет использование обработчика ожидания. Мы откажемся от жесткого цикла и будем открывать формы последовательно с небольшой задержкой (например, в 1–2 секунды). За это время фоновое задание предыдущей формы успеет отработать и завершиться.

Реализуем этот алгоритм по шагам:

  1. Создадим временное хранилище (массив или таблицу) на клиенте, куда поместим список всех строк, которые необходимо открыть.
  2. Определим переменную-индекс, которая будет указывать на текущую обрабатываемую строку.
  3. Подключим однократный обработчик ожидания.
  4. В процедуре обработчика будем открывать одну форму, сдвигать индекс и снова подключать обработчик, пока не обойдем весь массив.

Посмотрим на пример реализации данного подхода:


&НаКлиенте
Перем ОчередьНаОткрытие; // Массив структур с данными для открытия
&НаКлиенте
Перем ТекущийИндексОчереди; // Число, индекс текущего элемента

&НаКлиенте
Процедура КомандаЗагрузить(Команда)
    
    ОчередьНаОткрытие = Новый Массив;
    ТекущийИндексОчереди = 0;
    
    // Заполняем очередь только теми строками, которые выбрал пользователь
    Для каждого стр Из ТаблицаЗагрузить Цикл
        Если стр.Надо Тогда
            ДанныеСтроки = Новый Структура;
            ДанныеСтроки.Вставить("Организация", стр.Организация);
            ДанныеСтроки.Вставить("БанковскийСчет", стр.БанковскийСчет);
            ДанныеСтроки.Вставить("Файл", стр.Файл);
            
            ОчередьНаОткрытие.Добавить(ДанныеСтроки);
        КонецЕсли;
    КонецЦикла;
    
    // Если очередь не пуста, запускаем процесс циклического открытия
    Если ОчередьНаОткрытие.Количество() > 0 Тогда
        ПодключитьОбработчикОжидания("ОткрытьСледующуюФормуИзОчереди", 0.1, Истина);
    КонецЕсли;
    
КонецПроцедуры

&НаКлиенте
Процедура ОткрытьСледующуюФормуИзОчереди()
    
    Если ТекущийИндексОчереди >= ОчередьНаОткрытие.Количество() Тогда
        // Все формы открыты, очищаем переменные
        ОчередьНаОткрытие = Неопределено;
        Возврат;
    EndIf;
    
    ТекущиеДанные = ОчередьНаОткрытие[ТекущийИндексОчереди];
    
    СтруктураПараметров = Новый Структура;
    СтруктураПараметров.Вставить("РежимПоУмолчанию", "ГруппаЗагрузка");
    СтруктураПараметров.Вставить("Организация", ТекущиеДанные.Организация);
    СтруктураПараметров.Вставить("БанковскийСчет", ТекущиеДанные.БанковскийСчет);
    СтруктураПараметров.Вставить("ФайлЗагрузки", ТекущиеДанные.Файл);
    
    // Используем индекс в качестве ключа уникальности формы
    УникальныйКлючФормы = "КлиентБанк_" + СтрЗаменить(Строка(ТекущиеДанные.БанковскийСчет), " ", "");
    
    Попытка
        Форма1 = ОткрытьФорму("Обработка.КлиентБанк.Форма", СтруктураПараметров, , УникальныйКлючФормы, ВариантОткрытияОкна.ОтдельноеОкно, , , РежимОткрытияОкнаФормы.Независимый);
        Если Форма1 <> Неопределено Тогда
            Форма1.Заголовок = "Загрузка: " + ТекущиеДанные.БанковскийСчет;
        КонецЕсли;
    Исключение
        // Записываем ошибку в Журнал регистрации, чтобы не прерывать весь процесс
        ЗаписатьОшибкуВЛог(ОписаниеОшибки());
    КонецПопытки;
    
    // Переходим к следующему элементу
    ТекущийИндексОчереди = ТекущийИндексОчереди + 1;
    
    // Подключаем следующий шаг с небольшой задержкой (например, 1.5 секунды)
    // Этого времени достаточно, чтобы серверное фоновое задание успело инициализироваться и завершиться
    ПодключитьОбработчикОжидания("ОткрытьСледующуюФормуИзОчереди", 1.5, Истина);
    
КонецПроцедуры

---

Решение 2. Модернизация кода фонового задания (через Расширение конфигурации)

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

Для этого выполним следующие действия:

  1. Проанализируем модуль формы или менеджер обработки КлиентБанк на сервере. Нам нужно найти вызов метода ФоновыеЗадания.Выполнить().
  2. Обратим внимание на то, как именно формируется параметр Ключ. Скорее всего, там используется жесткая конструкция вида: КлючЗадания = "ОбменСБанком_" + Параметры.Организация;
  3. Нам необходимо сделать этот ключ уникальным для каждой открываемой формы. Для этого мы можем передавать из клиентского метода дополнительный параметр — уникальный идентификатор формы. Переопределим вызов в расширении:

// В модуле формы обработки КлиентБанк при создании на сервере:
&После("ПриСозданииНаСервере")
Процедура ИНФ_ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    // Получаем уникальный идентификатор текущей формы
    ЭтотУникальныйИдентификатор = ПолучитьСсылкаНового(); // Или Строка(УникальныйИдентификатор)
    // Сохраняем его в параметрах запуска фонового процесса
КонецПроцедуры

При генерации фонового задания добавим этот идентификатор в ключ запуска:


// Вместо старого ключа формируем новый, абсолютно уникальный для данного окна:
КлючЗадания = "ОбменСБанком_" + Параметры.Организация + "_" + Параметры.ИдентификаторФормы;
ФоновыеЗадания.Выполнить(ИмяМетода, ПараметрыВыполнения, КлючЗадания, "Загрузка данных банка");

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

---

Решение 3. Лучшая практика (Best Practice) — уход от интерактивного открытия окон

Давайте подумаем: насколько удобно пользователю работать, если на экране одновременно откроется 10, 15 или 20 окон обработки «Клиент-Банк»? Скорее всего, пользователю придется в каждом окне совершать однотипные клики мышкой (например, нажимать кнопку «Загрузить»). Это неэффективно и сильно перегружает оперативную память как клиентского компьютера, так и сервера приложений 1С.

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


&НаСервере
Процедура ВыполнитьПрограммнуюЗагрузкуНаСервере(ТаблицаДанных)
    
    Для каждого СтрокаДанных Из ТаблицаДанных Цикл
        Если Не СтрокаДанных.Надо Тогда
            Продолжить;
        КонецЕсли;
        
        // Создаем объект обработки программно, без визуализации
        ОбъектОбработки = Обработки.КлиентБанк.Создать();
        
        // Заполняем реквизиты обработки данными из нашей таблицы
        ОбъектОбработки.Организация      = СтрокаДанных.Организация;
        ОбъектОбработки.БанковскийСчет   = СтрокаДанных.БанковскийСчет;
        ОбъектОбработки.ИмяФайлаЗагрузки = СтрокаДанных.Файл;
        
        // Настраиваем параметры программного выполнения (зависит от логики конфигурации)
        ОбъектОбработки.РежимРаботы = "Загрузка";
        
        Попытка
            // Вызываем экспортный метод загрузки, который обычно отрабатывает на сервере
            ОбъектОбработки.ВыполнитьЗагрузку(); 
            
            // Фиксируем успешный результат в лог
            ЗаписатьИнформациюВЛог(СтрокаДанных.БанковскийСчет, "Успешно загружено");
        Исключение
            // Если произошла ошибка, фиксируем её и идем дальше по циклу
            ЗаписатьОшибкуВЛог(СтрокаДанных.БанковскийСчет, ОписаниеОшибки());
        КонецПопытки;
        
    КонецЦикла;
    
КонецПроцедуры

При таком подходе пользователь запускает процесс один раз нажатием одной кнопки. Система на сервере последовательно (или даже в один поток) обрабатывает все файлы выписок. В завершение процесса пользователю выводится лаконичный отчет: какие счета обработаны успешно, а по каким возникли ошибки (например, не найден файл выписки). Это экономит ресурсы, исключает возникновение блокировок фоновых заданий и существенно упрощает работу конечного пользователя.

← На главную