DeferEvent.h

См. документацию.
00001 /*
00002  * CntmLib - Подсчет ссылок, потоки, синхронизация, асинхронные процедуры, события
00003  * Copyright (c) 2005, Овсеевич Роман, CntmLib@mail.ru
00004  * _______________________________________________________________________________
00005  * Разрешено свободное использование, копирование, распространение, изменение
00006  * (изменение сведений об авторских правах запрещено). Запрещена продажа и 
00007  * включение всей библиотеки или ее частей в другие библиотеки. В сведениях об
00008  * авторских правах на программу (или сведениях о программе, об авторах, 
00009  * использованных средствах разработки и т.д.) должна быть указана информация
00010  * о библиотеке CntmLib, ее авторе и, возможно, сайте или email'е.
00011  * Библиотека поставляется "как есть", без каких-либо гарантий со стороны автора.
00012  */ 
00013 
00014 #ifndef CNTM_DEFEREVENT_H
00015 #define CNTM_DEFEREVENT_H
00016 #include <Cntm/Utils/SignatureArgsCollector.h>
00017 #include <Cntm/Utils/SignatureInfo.h>
00018 #include <Cntm/Events/Internal/BasicDeferEvent.h>
00019 
00020 namespace Cntm
00021 {       
00022         
00023         /**
00024          * Шаблонный класс отложенного синхронного (сериализованного) события. 
00025          * 
00026          * Под событием, в данном случае, понимается способ оповещения сервером заинтересованной стороны (клиентов) об изменении своего состояния. Для того, чтобы клиент мог обрабатывать событие, он должен создать связь между объектом события и обработчиком. В качестве обработчиков вуступают методы объектов, т.е. обработчик задается указателем на объект и указателем на метод. С одним событием м.б. связано несколько обработчиков. При генерации события указываются аргументы, которые получат обработчики события (т.е. методы). В процессе генерации события быдут вызваны все обработчики с указанными аргументами.
00027          * 
00028          * Класс Cntm::DeferEvent является шаблоном. В качестве параметра шаблона указывается сигнатура функции (т.е. без указания конкретного класса обработчика), которая будет использоваться при генерации события. Пример объвления события: 
00029          * DeferEvent<void (int,const string&,Class1*,const Class2::Ptr&)> ChangeEvent;
00030          * Методы обработчиков должны иметь такую же сигнатуру, как и событие. При попытке связать событие с собработчиком с другой сигнатурой будет выдана ошибка компиляции, т.е. обеспечивается жесткая проверка типов. Возвращаемое значение (если его тип не void) при вызове обработчиков игнорируется. Обычно объект события объявляется как public поле объекта, генерирующего событие.
00031          * 
00032          * Свзывание события с обработчиком осуществляется с помощью специального класса Cntm::EventLink (не является шаблоном). Что бы создать связь нужно создать объект этого класса и указать событие и обработчик (указатель на объект, указатель на метод и дополнительные параметры). При уничтожении этого объекта связь будет автоматически разорвана. Также у этого класса есть метод Attach(), позволяющий изменить связь и имеющий такие же аргументы, что и конструктор (при этом старая связь разрывается и создается новая), а также метод Detach(), разрывающий текущую связь. Рассмотрим пример:
00033          * EventLink onChangeClass1(object1->ChangeEvent, this, &ThisClass::OnChangeClass1);
00034          * onChangeClass1.Attach(object2->ChangeEvent, this, &ThisClass::OnChangeClass2);
00035          * onChangeClass1.Detach();
00036          * 
00037          * Генерация события производится с помощью оператора operator() объекта события. В качестве аргументов этого оператора указываются аргументы, которые будут переданы обработчикам. Пример:
00038          * onChangeClass1(45, "Привет всем обработчикам", this, object2);
00039          * 
00040          * После общего обзора перейдем к рассмотрению различных аспектов.
00041          * 
00042          * В названии класса говорится, что это отложенное (сериализованное) событие. Это значит, что обработчики события будут вызваны не в момент генерации события (т.е. не в операторе operator()), а когда-то потом. Зачем "потом"? Концепция обработки событий подразумевает, что клиенты, обрабатывающие события, знают о свойствах и поведении сервера, в то время, как сервер не знает о поведении обработчиков своих событий в клиентах (по крайней мере, если проектировать сложные системы так, чтобы сервер знал о поведении обработчиков, то это еще больше усложнит процесс проектировки и снизит гибкость при доработке). Например, если событие генерируется в какой-то нестабильной точке алгоритма, то обработчик может вызвать такой метод сервера, выполнение которого приведет к ошибкам в данных сервера, т.к. произойдет реентерабельный вход в объект, который, возможно, не был предусмотрен. Т.о. при прямой генерации событий нарушается естественный порядок выполнения алгоритма. Что бы избежать проблем, следует четко определять безопасные места генерации событий, применять спец. средства борьбы с реентерабельным входом или просто запрещать вызовы определенных методов из обработчиков. Отложенные события лишены этого недостатка, они не нарушают естественного хода выполнения алгоритма, т.к. в момент генерации происходит только фиксация аргументов события, а вызов обработчиков произойдет после завершения алгоритма. Однако, за все надо платить. Недостатком отложенных событий является то, что между моментом генерации и моментом начала обработки проходит некоторое время, за которое состояние сервера может измениться. Одним из способов борьбы с этим недостатком является передача состояния в качестве аргументов события. Т.о. если для обработки события нужно текущее состояние сервера, а сам обработчик не меняет текущее состояние сервера, то можно использовать обычные события (назовем их прямыми), в противном случае рекомендуется использовать отложенные события (которые имеют и дополнительные плюсы в многопоточных системах).
00043          * 
00044          * В названии класса сказано, что это синхронное событие, это значит, что оно тесно связано с понятием синхропространства (ознакомтесь с документацие по классу Cntm::SynchroSpace). Каждый обработчик события выполняется внутри определенного синхропространства (которое указывается тем или иным способом при свзывании события с обработчиком), вход в которое произведен в реентерабельном режиме. Если событие сгенерировано в некотором синхропространстве, то гарантируется, что вход в это синхропространство из обработчиков события будет произведен только после выхода из него потока выполнения алгоритма, который сгенерировал событие, причем, не имеет значение, в каком синхропространстве выполняется обработчик (в этом-же или другом). Т.о. обеспечивается защита от реентерабельного входа в объект, сгенерировавший событие (т.к. вход невозможен во все синхропространство, к которому принадлежит объект). Это правило имеет исключение, когда поток выполнения алгоритма вошел в синхропространство в реентерабельном режиме и произвел реентерабельный вход в синхропространство. В этом случае обработчики могут произвести свой вход в синхропространство в момент реентерабельного входа. Т.о. ход выполнения алгоритма может быть нарушен, но только при жестко определенных условиях: режим входа в синхропространство - реентерабельный и выполнение реентерабельного входа в синхропространство. Еще раз подчеркнем, что обработчик выполняется не в том синхропространстве, в котором сгенерировано событие (оно м.б. сгенерировано и вне синхропространства), а в указанном при связывании.
00045          * 
00046          * Рассмотрим пример системы, использующей библиотеку QT (см. документацию по этой библиотеке). Для создания главного синхропространства, интегрированного с QT следует мспользовать класс Cntm::QTMainSynchroSpace (см. документацию по этому классу), прочих синхропространств в системе нет.  Приложение выполняется, когда запущен цикл обработки сообщений (метод QApplication::exec()). Будем считать реентерабельным входом в главное синхропространство отображение модальных окон, т.е. когда происходит запуск вложенного цикла обработки сообщений. В этом случае если где-то (в том числе и не в главном потоке) будет сгенерировано событие, то обработчики будут выполнены синхронно с остальными событиями QT, через цикл обработки событий в главном потоке, причем после того, как произойдет возврат в цикл обработки. Т.е. обработка отложенных событий ни чем не отличается от событий QT, таких, как событие нажатия кнопки или событие таймера.
00047          * 
00048          * Подводя итог всем этим обяснениям, можно сказать, что обработчик отложенного события всегда выполняется изолированно от генерации события. В каком бы потоке не было бы сгенерировано событие, обработчик всегда будет вызываться синхронизированно, в частности, если обработчик связан с главным синхропространством QT, то его вызов всегда будет синхронизирован с другими событиями QT (конкретнее, он будет выполнен в цикле обработки событий QT).
00049          * 
00050          * Теперь подробнее о связях событий с обработчиками. Класс Cntm::EventLink имеет конструктор по умолчанию, который инициализирует пустую связь. Конструкторы связывания и методы Attach() имеют 3 обязательных параеметра и 1 опциональный. Рассмотрим их:
00051          * - 1 параметр Event - ссылка на объект события, т.е. должен передаваться объект события, с которым происходит связывание;
00052          * - 2 параметр Object - указатель на объект, который будет обрабатывать событие. Можно передвать либо ссылочный указатель либо обычный. Если указатель равен NULL, то генерируется исключение Cntm::NullArgException;
00053          * - 3 параметр Method - указатель на метод обработчика. Сигнатура метода должна полностью совпадать с сигнатурой события;
00054          * - 4 параметр Space (опциональный) - синхропространство, в котором будет выполняться обработчик. Если оно явно не указано, то определяется следующим образом. Если объект обработчика является синхрообъектом (см. класс Cntm::SynchroObject), то синхропространство берется из свойства Space() этого объекта. В противном случае используется главное синхропространство (см. Cntm::SynchroSpace::Main()). Если главного синхропространства нет (Cntm::SynchroSpace::Main() возвращает NULL), то генерируется исключение Cntm::IllegalStateException.
00055          * 
00056          * Время жизни объектов и подсчет ссылок. При создании связи между событием и обработчиком происходит увеличение кол-ва ссылок на синхропространство, в котором производится обработка. Т.о. связи событий могут удерживать синхропространства, поэтому для нормального завершения работы необходимо уничтожить все связи. Рекомендация: наиболее целесообразно создавать связи как поля класса, который содержит обработчики событий. В этом случае вместе с объектом будут разорваны и все связи для объекта. На объект, обрабатывающий событие (если это объект с подсчетом ссылок, наследующий Cntm::IRefObject) кол-во ссылок не увеличивается (что бы не препятствовать его удалению). Если после создания связи и до ее разрыва произойдет уничтожение объекта события, ничего страшного не случится, просто событие больше никогда не будет сгенерировано. Если во время существования связи произойдет уничтожение объкта обработчика, то это приведет к ошибкам выполнения, поэтому время жизни связи не должно превышать время жизни объекта обработчика.
00057          * 
00058          * Процесс генерации события. При генерации вызываются только те обработчики, которые были связаны с событием до генерации, гарантируется, что обработчики добавленные после генерации, вызваны не будут. Если связь разрушена после генерации события, но до вызова обработчика, то гарантируется, что обработчик вызван не будет. Для объектов обработчиков, являющихся объектами с подсчетом ссылок: если на момент вызова обработчика события на объект не осталось ссылок, но он еще существует (находится в удаляемом состоянии, см. Cntm::IRefObject), то вызов обработчика произведен не будет, если вызов обработчика произведен, то гарантируется наличие хотя бы одной ссылки на объект обработчика до выхода из метода обработки. Исключения, сгенерированные в обработчиках, ни как не влияют на вызов других обработчиков, они просто игнорируются. В процессе вызова обработчиков имеется только одна копия сохраненных аргументов события, поэтому: 1. изменения аргументов коснутся всех обработчиков, которые будут вызваны после изменившего; 2. обработчики могут быть вызваны одновременно в разных потоках, поэтому и доступ к одному и тому же значению аргумента может производиться из разных потоков.
00059          * 
00060          * Данный класс обеспечивает многопоточность.
00061          * @author Овсеевич Р.
00062          * \ingroup Events
00063          */
00064         template < typename SignatureT >
00065         class DeferEvent: public SpecUtils::BasicDeferEvent, public SignatureArgsCollector < DeferEvent < SignatureT > , void, SignatureInfo < SignatureT >, SignatureInfo < SignatureT > ::argsCount >
00066         {
00067         private:
00068         
00069                 friend class SignatureArgsCollector<DeferEvent<SignatureT>, void, SignatureInfo<SignatureT>, SignatureInfo<SignatureT>::argsCount>;
00070 
00071                 typedef SignatureArgsCollectObjectBase <typename SignatureInfo<SignatureT>::Args> BasicSignatureArgsCollectObject;
00072 
00073                 /**
00074                  * Класс объекта вызова. Является ссылочным объектом. Хранит копии значений аргументов генерации события и знает как вызвать указанный обработчик и как подставить хранимые аргументы. Уничтожается автоматически после выполнения всех синхрозадач вызова обработчиков. 
00075                  * @author Овсеевич Р.
00076                  */
00077                 class SignatureArgsCollectObject: public BasicCallObject, public BasicSignatureArgsCollectObject
00078                 {
00079                 public:
00080                 
00081                         typedef typename SignatureInfo<SignatureT>::template Method<HandlerType::Closure>::Pointer MethodPointer;
00082                         
00083                         /**
00084                          * Вызвать обработчик. Выполняет преобразование в конкретный указатель на метод и взывает его с сохраненными аргументами
00085                          */
00086                         void ExecHandler(const HandlerType& Handler)
00087                         {
00088                                 SignatureInfo<SignatureT>::MethodCall(Handler.Object(), reinterpret_cast<MethodPointer>(Handler.Method()), 
00089                                         BasicSignatureArgsCollectObject::Args());
00090                         }
00091                         
00092                 };
00093                 
00094                 /**
00095                  * Создать объект вызова. Просто создает в куче объект типа SignatureArgsCollectObject. Вызывается из AignatureArgsCollector::operator(). 
00096                  */
00097                 SignatureArgsCollectObject* CreateCollectObject() { return new SignatureArgsCollectObject; }
00098                 
00099                 /**
00100                  * Обработать объект вызова. Вызывает метод BasicDeferEvent::Call() для генерации события. Вызывается из AignatureArgsCollector::operator(). 
00101                  */
00102                 void ProcessCollectObject(SignatureArgsCollectObject* Object) { Call(BasicCallObject::Ptr(Object)); }
00103 
00104                 /**
00105                  * Проверяет, нужно ли обрабатывать вызов для генерации события. Если обработчиков нет, то никаких действий не выполняется. Вызывается из AignatureArgsCollector::operator(). 
00106                  */
00107                 bool NoNeedCreateCollectObject() { return NoNeedCall(); }
00108                 
00109                 /**
00110                  * Ничего не делает. Нужна для AignatureArgsCollector::operator(). 
00111                  */
00112                 void NoneProcessCollectObject() {}
00113         };
00114 
00115 }
00116 
00117 #endif //CNTM_DEFEREVENT_H

SourceForge.net Logo
© Овсеевич Р.В. Документация по CntmLib 1.1.4 от 28 May 2008. Создано системой  doxygen 1.5.3