Главная » 1С Предприятие » ПРЕОДОЛЕНИЕ КОНФЛИКТОВ

0

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

жз.3аписать();

{D:\ПPOБA.ERT(20)}: Объект  заблокирован: Журнал  расчетов Журнал заработной платы Чтобы  промоделировать  такую  ситуацию,  напишем  следующий  код:

процедура Выполнить(  )                       //              Связана с кнопкой Пуск  обработки Проба

перем сСотр_2, сотр, жз, рез, флаг; ОчиститьОкноСообщений( );

сСотр_2 = СоздатьОбъект("Справочник.Сотрудники_2");

// Ищем  во всем справочнике сотрудника с кодом 301 сСотр_2.НайтиПоКоду(301,0);

сотр  = сСотр_2.ТекущийЭлемент( );

жз  = СоздатьОбъект("ЖурналРасчетов.Зарплата_2");

// Открываем выборку расчетов объекта, зарегистрированных в текущем периоде жз.ВыбратьПериодПоОбъекту(сотр);

пока жз.ПолучитьЗапись( ) = 1 цикл

// Пропускаем фиксированные и исправленные расчеты если  жз.Фиксирована  +  жз.Исправлена о  0  тогда

продолжить; конецЕсли;

рез = жз.Результат;

// Начинаем  бесконечный цикл

// Чтобы цикл прервать, нужно нажать клавишу Esc

// В этом цикле время от времени захватывается ЖЗ флаг= 1;

пока  флаг = 1 цикл

// Значение реквизита Результат не меняется жз.УстановитьРеквизит("Результат",  рез); жз.3аписать();

конецЦикла; // пока флаг = 1 конецЦикла; // пока жз.ПолучитьЗапись() = 1

конецПроцедуры // Выполнить

Эта программа никаких изменений в ЖЗ не производит, но время от времени, выполняя оператор

жз.3аписать();

захватывает журнал  расчетов Зарплата_2.

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

// Ищем во всем справочнике сотрудника с кодом 301 сСотр_2.НайтиПоКоду(301,0);

имеющую оператор

// Ищем во всем справочнике сотрудника с кодом 302 сСотр_2.НайтиПоКоду(302, 0);

То есть в ЖЗ мы будем обновлять результат  другой записи, отвечающей  объекту с кодом 302.

Тогда  если  сотрудники  с кодами  301  и  302 в  справочнике Сотрудники_2  есть и если  в  текущем  периоде ЖЗ  каждый  из этих сотрудников  имеет  нефиксированный  и  неисправленный  расчет, то  неизбежно  в  работе  одного  из  пользователей  возникнет  завершающая  ошибка  исполнения  и   приведенное   выше  сообщение об этой ошибке.

Если же в этом  эксперименте вместо операторов

жз.УстановитьРеквизит("Результат",  рез); жз.3аписать();

записать  оператор жз.Результат = рез;

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

жз.Результат = рез;

{D:\ПPOБA.ERT(21)}: Запись заблокирована!

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

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

Чтобы  продемонстрировать метод  устранения  такой  ошибки,  продолжим  начатый пример, модифицировав  в  нем  бесконечный цикл Пока  флаг = 1, введя в  него  управляющую  конструкцию  Попытка.

// Начинаем бесконечный цикл

// Чтобы цикл прервать, нужно нажать  клавишу Esc

// В этом цикле время от времени захватывается ЖЗ флаг =1 ;

пока  флаг = 1 цикл                           //                Этот цикл подлежит модификации

// Значение реквизита Результат не меняется жз.УстановитьРеквизит("Результат",  рез);

// Начало добавляемого кода

// Вводим  в цикле Пока флагПопытки 1 управляющую конструкцию  Попытка

// Цикл прервется после удачного  исполнения метода Записать флагПопытки = 1;

пока флагПопытки = 1 цикл

попытка                    //                      Управляющая конструкция Попытка жз.3аписать();                  //            Этот оператор из старого  кода флагПопытки = 0;

Сообщить("Запись выполнена.");

исключение

Сообщить("Ожидаю  освобождения таблицы."); конецПопытки;

конецЦикла; // пока флагПопытки = 1

// Конец добавляемого кода конецЦикла; // пока флаг = 1

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

Запись выполнена.

Запись выполнена.

Ожидаю освобождения таблицы. Ожидаю освобождения таблицы. Запись выполнена.

Продемонстрированный  метод  преодоления   конфликтов  при  помощи  управляющей  конструкции  Попытка нужно  применять  каждый  раз, когда  есть  угроза  пользовательских конфликтов  на почве захвата DBF-таблиц. В частности,  при  работе  такая  угроза  существует при употреблении  методов, выполняющих  модификацию ЖЗ, а именно:   ВыполнитьРасчет,  Рассчитать,  ВвестиРасчет,  ЗаписатьРасчет,  ВвестиПерерасчет, Записать,  ФиксироватьЗапись,  ОсвободитьЗапись,  Исправить,  ОтменитьИсправление и  УдалитьЗапись.

С  учетом   сказанного  процедура  РасчетОбъекта,  рассчитывающая   записи  сотрудника, вызываемая  из процедуры РасчетЗП (разд.  7.17.1), должна  быть  усовершенствована следующим  образом:

// Обновленная процедура РасчетОбъекта

// Рассчитывает все записи объекта ЖЗ кроме расчета с ВР НачСальдо_2

// Поскольку процедура присутствует в модуле формы списка ЖЗ,

// то все методы ЖР вызываются без префикса процедура  РасчетОбъекта(сотр)

перем флаг, сотр2, флагПопытки;

// Флаг будет равен единице, если у сотрудника есть расчет с ВР Оклад_2  флаг = 0;

сотр2 = сотр;                             //                 Запоминаем для вывода сообщения

// Открываем  выборку расчетов объекта, зарегистрированных в текущем периоде ВыбратьПериодПоОбъекту(сотр);

пока ПолучитьЗапись() = 1 цикл

если видРасч = ВидРасчета.НДФЛ_2 тогда флаг = 1;

конецЕсли;

// Фиксированные и исправленные записи рассчитывать нет смысла  если Фиксирована + Исправлена = 0 тогда

// Начало добавляемого  кода

// Вводим в цикле Пока флагПопытки = 1 управляющую конструкцию Попытка

// Цикл прервется после удачного  исполнения метода Рассчитать флагПопытки = 1;

пока флагПопытки = 1 цикл

попытка                 //                    Управляющая конструкция Попытка Рассчитать();                //           Этот оператор из старого  кода флагПопытки = 0;

Состояние ("Выполнен расчет сотрудника " + сотр.Наименование);  исключение

Состояние("Ожидаю  освобождения таблицы.");

конецПопытки;

конецЦикла; // пока флагПопытки = 1

// Конец добавляемого кода

конецЕсли; конецЦикла; // пока если флаг = 0 тогда

Сообщить("Оформите табель сотруднику " + сотр2.Наименование); конецЕсли;

конецПроцедуры // РасчетОбъекта

Аналогичные  изменения должны  быть  внесены  и  в  другие процедуры модуля  формы списка ЖЗ  Зарплата_2, а также  в  модули  форм  созданных нами документов  и справочников, хотя степень их конфликтности по сравнению с ЖЗ существенно ниже.

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

конфликтностью.

8.2.2. ТРАНЗАКЦИИ

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

Если  речь  идет о единичном обновлении  базы  данных, например  в  результате  выполнения   метода   Записать,  то  гарантии   получения   ожидаемого  результата   нам   дает управляющая конструкция Попытка.  При  пакетном  обновлении  базы  данных, например  в  результате  проведения документа,  порождающего многочисленные расчеты  ЖЗ, нужно  применять  транзакцию.

Надо  сказать, что в  модуле  документа в  предопределенных процедурах  ОбработкаПроведения  и  ОбработкаУдаленияПроведения  1С  выполняет  предусмотренные в  этих процедурах действия,  употребляя транзакцию.  Поэтому  добавлять  в  них свои транзакции нет необходимости.

Известно,  что  транзакции  блокируют  используемые   в   них DBF-файлы,  поэтому необходимо   максимально   содействовать   их   успешному  завершению.   В   частности,

предопределенные   процедуры   ОбработкаПроведения  и   ОбработкаУдаленияПроведения  не  следует  включать  приостанавливающие до  отклика  пользователя   процедуры Вопрос,   Предупреждение,  функции,   вызывающие   диалог  ввода данных,  например ВвестиЗначение,  и другие  открывающие диалоги  команды.

В  решаемых нами  задачах,  кроме   упомянутых процессов  проведения  документов, транзакцию   можно   было   бы  применить  при  расчете   зарплаты  объекта,  записав  код процедуры РасчетОбъекта следующим  образом:

// Еще один вариант процедуры РасчетОбъекта, на этот раз использующий транзакцию  процедура  РасчетОбъекта(сотр)

перем  флаг, сотр2;

// Флаг будет равен единице, если у сотрудника есть расчет с ВР Оклад_2  флаг = 0;

сотр2 = сотр;                       //             Запоминаем для вывода сообщения

// Открываем  выборку расчетов объекта, зарегистрированных в текущем периоде если ВыбратьПериодПоОбъекту(сотр) = 0 тогда

возврат;

конецЕсли;

НачатьТранзакцию();    //          Первый добавленный оператор..

пока ПолучитьЗапись() = 1 цикл

если видРасч = ВидРасчета.НДФЛ_2 тогда флаг = 1;

конецЕсли;

// Фиксированные и исправленные записи рассчитывать нет смысла если Фиксирована + Исправлена = 0 тогда

Рассчитать( ); конецЕсли;

конецЦикла; // пока

если флаг = 1 тогда                 //          Транзакция фиксируется, если проведен Табель ЗафиксироватьТранзакцию();  // Второй  добавленный оператор

иначе                            //                флаг = 0

Отменить     Транзакцию();    //  Третий добавленный оператор Сообщить("Оформите табель сотруднику " + сотр2.Наименование);

конецЕсли;

конецПроцедуры // РасчетОбъекта

В этом  обновленном коде  3 новые  (для  нас)  встроенные  в  1С  процедуры:  НачатьТранзакцию, ЗафиксироватьТранзакцию  и  ОтменитьТранзакцию.  Назначение процедур понятно из их названий. Остановимся на особенностях их исполнения.

Во-первых,   пользователь,   начавший   транзакцию,   захватывает  одну   или   несколько  DBF-таблиц,  участвующих в   транзакции,  и  тем  самым   Не  дает  работать  другим участникам  процесса.  При  длительном  захвате таблицы  ждущая  процедура может  завершиться  аварийно,  о  чем  пользователь   получит  соответствующее  сообщение, например  такое:

пока ПолучитьЗапись( ) = 1 цикл

{ЖурналРасчетов.Зарплата_2.Форма.ФормаСписка.Форма.Модуль(250)}:Таблица: CJ4287

Ошибка обращения к данным  при транзакции, выполняемой другим пользователем

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

Во-вторых, обновление захваченных таблиц во время транзакции не производится, но  необходимые  для  обновления  сведения накапливаются  в  надлежащем виде  для последующего использования.

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

В нашем  примере осуществлены  вызовы всех трех процедур, что несколько изменило характер  получаемого результата. Так, если ранее процедура РасчетОбъекта модифицировала результаты расчетов и при отсутствии расчета  с ВР Оклад_2, то теперь в  такой  ситуации данные  не  меняются, поскольку вызывается .процедура ОтменитьТранзакцию.

Если  сравнивать две модификации  процедуры РасчетОбъекта  (первая  с  управляющей конструкцией Попытка, а вторая с транзакцией), то предпочтение следует, видимо, отдать первому варианту. На этот счет есть по меньшей мере 3 причины. Первая в том, что нет необходимости рассматривать расчет  объекта  как единое целое  и употреблять  для него транзакцию;  вполне приемлемо, если расчеты  будут выполняться последовательно   в  соответствии  с установленной очередностью, и  нет  трагедии,  если между двумя вашими расчетами  выполнится один-другой расчет иного пользователя. Во-вторых, как мы убедились, при длительных транзакциях  ждущие процедуры могут завершаться  аварийно. И наконец, транзакция  может потребовать дополнительных ресурсов (пространственных, например  в виде области памяти на диске, и временных), снижающих эффективность исполнения программы.

Источник: Бартеньев О. В. 1С:Предприятие:  программирование для  всех.  Базовые объекты и расчеты на одной дискете. М.: Диалог-МИФИ, 2005. 464 с.

По теме:

  • Комментарии