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
© Овсеевич Р.В. Документация по CntmLib 1.1.4 от 28 May 2008. Создано системой 1.5.3 |