Listener
Summary
Listener是除WaitSet之外实现推送方法来检测特定事件并对其作出反应。监听器为用户提供了附加具有相应事件和回调的对象的能力。每当对象接收到指定的事件时,就会在Listener后台线程中调用相应的回调作为响应。
WaitSet的两个关键区别是侦听器是事件驱动的而不是像WaitSet和Listener创建的那样由事件和状态驱动一个单独的后台线程,在该线程中执行事件回调,而在WaitSet中用户必须显式调用事件回调。
##术语
-条件变量
由附加对象用于通知Listener/WaitSet事件发生。
-事件正在更改对象的状态。
-事件驱动由事件直接引起的一次性反应。
示例:一个新样本已发送给一个订阅者。
-状态对象成员设置为的预定义值。
-状态驱动重复反应,该反应持续到状态持续存在。
示例:订阅者存储了未经用户检查的样本。
Design
监听器是 反应器模式 的变体用法应该类似于_WaitSet_,但有一个关键区别——它应该是事件驱动的,而不是事件和状态驱动的混合,这取决于附加的事件,就像在_WaitSet中一样。
Requirements
- 每当事件发生时,应尽快调用相应的回调一次。
- 如果某个事件在调用回调之前发生多次,则回调应调用一次。
- 如果在执行回调时发生事件,则应再次调用回调一次。
– 线程安全:在任何时间从任何线程附加事件。
– 线程安全:在任何时间从任何线程分离事件。 - 如果回调当前正在运行“detachEvent”块,则直到回调完成。
- 在“detachEvent”调用之后,不再调用事件回调,甚至当在运行“detachEvent”时发出事件信号,并且尚未执行回调。
- 如果回调是从事件回调中分离的,则
detachEvent
是非阻塞的。事件在“detachEvent”调用之后立即分离。 - 对于特定对象的特定事件,最多可以附加一个回调。
- 通常由开发人员使用枚举定义。一个例子是“SubscriberEvent::DATA_RECEIVED”。
- 将回调附加到已附加回调的事件会导致错误。
- 可以将同一事件同时附加到不同的对象。
- 可以将多个不同的事件附加到单个对象
- 当侦听器超出作用域时,它会将自己从每个附加的对象(如在WaitSet中)。
- 当附加到侦听器的类超出作用域时通过侦听器提供的回调将自身与侦听器分离(就像在WaitSet)。
Class diagram
+---------------------------+
| ConditionVariableData |
| - m_semaphore |
| - m_runtimeName |
| - m_toBeDestroyed |
| - m_activeNotifications |
+---------------------------+
| 1 | 1
| |
| 1 | n
+-----------------------------------------------+ +--------------------------------------------------+
| ConditionListener | | ConditionNotifier |
| ConditionListener(ConditionVariableData & ) | | ConditionNotifier(ConditionVariableData &, |
| | | uint64_t notificationIndex) |
| bool wasNotified() | | |
| void destroy() | | void notify() |
| NotificationVector_t wait() | | |
| NotificationVector_t timedWait() | | - m_condVarDataPtr : ConditionVariableData* |
| | | - m_notificationIndex |
| - m_condVarDataPtr : ConditionVariableData* | +--------------------------------------------------+
| - m_toBeDestroyed : std::atomic_bool | | 1
+-----------------------------------------------+ |
| 1 | n
| +--------------------------------+
| 1 | TriggerHandle |
+-------------------------------------------------+ | bool isValid() |
| Listener | | bool wasTriggered() |
| attachEvent(Triggerable, EventType, Callback) | | void trigger() |
| detachEvent(Triggerable, EventType) | | void reset() |
| | | void invalidate() |
| - m_events : Event_t[] | | void getUniqueId() |
| - m_thread : std::thread | | |
| - m_conditionListener : ConditionListener | | - m_conditionVariableDataPtr |
| | | - m_resetCallback |
| +----------------------------+ | | - m_uniqueTriggerId |
| | Event_t | | +--------------------------------+
| | void executeCallback() | | | 1
| | bool reset() | | |
| | bool init(...) | | | n
| | | | +-------------------------------------------------------+
| | - m_origin | | | Triggerable (e.g. Subscriber) |
| | - m_callback | | | |
| | - m_invalidationCallback | | | void invalidateTrigger(const uint64_t triggerId) |
| | - m_eventId | | | void enableEvent(TriggerHandle&&, const EventEnum ) |
| +----------------------------+ | | void enableEvent(TriggerHandle&&) |
+-------------------------------------------------+ | void disableEvent(const EventEnum) |
| void disableEvent() |
| |
| - m_triggerHandle : TriggerHandle |
+-------------------------------------------------------+
Triggerable不需要实现所有的“enableEvent”,disableEvent
变体,仅限使用所需的变体案例“enableEvent”和“disableEvent”,没有区别的“EventEnum”,可以在只有一个事件可以触发时使用。
Class Interactions(类交互)
- 创建侦听器: 在共享内存中创建“ConditionVariableData”。“侦听器”使用“ConditionListener”来等待传入事件。
PoshRuntime
Listener |
| getMiddlewareConditionVariable : var |
| --------------------------------------> |
| ConditionListener(var) | ConditionListener
| ----------------------------------------+-----------------> |
| wait() : vector<uint64_t> | |
| ----------------------------------------+-----------------> |
- 将可触发事件(SubscriberEvent::DATA_RECEIVED)附加到侦听器 侦听器创建一个TriggerHandle,并通过“enableEvent”将其提供给可触发(订阅服务器),以便可触发拥有该句柄。每当事件发生时,Triggerable都可以使用TriggerHandle的“trigger()”方法来通知Listener。
User Listener Triggerable
| attachEvent() | |
| ------------------> | TriggerHandle |
| | create | |
| | ---------> | |
| | enableEvent(std::move(TriggerHandle)) |
| | -----------+--------------------------------------> |
- 从Triggerable发出事件信号: 调用“TriggerHandle::trigger()”时侦听器正在从
ConditionListener.wait()
调用返回并检索所有信号通知的列表。相应的事件回调被调用。
Triggerable TriggerHandle ConditionNotifier ConditionListener Listener Event_t
| trigger() | | | wait() : notificationIds | |
| -------------> | notify() | | <------------------------- | |
| | -------------> | .... unblocks .... | blocks | exeuteCallback() |
| | | | | ------------------------> |
| | | | | m_events[notificationId] |
- Triggerable超出范围: TriggerHandle是Triggerable的成员,因此调用了TriggerHandle的d’tor,它通过
resetCallback从侦听器中删除触发器
Triggerable TriggerHandle Listener Event_t
| ~TriggerHandle | | |
| ----------------> | removeTrigger() | |
| | ----------------> | reset() |
| | via resetCallback | ------------> |
- 侦听器超出范围: “Event_t”的“tor”通过“invalidationCallback”使Triggerable内的触发器无效`
Listener Event_t Triggerable
| ~Event_t() | |
| -----------> | invalidateTrigger() |
| | -----------------------> |
| | via invalidationCallback |
TriggerHandle
-
问题: Triggerable应该能够在不了解这些类的情况下通知Listener/WaitSet,这样就可以防止循环依赖。此外,Triggerable必须能够在超出范围时删除其附加的事件。
-
解决方案: 依赖反转原则,创建一个双方都知道的抽象,即TriggerHandle。由Listener/WaitSet创建并附加到Triggerable,以便它可以通过具有“TriggerHandle::notify()”的底层“ConditionNotificationer”通知Listener/WaitSet。
清理任务由“m_resetCallback”执行,因此Triggerable不依赖于任何Notifyable。
Condition Variable
“ConditionListener”和“ConditionNotificationer”是同一类的两个不同接口,状态存储在“ConditionVariableData”类中。分离的目的是只提供一侧(例如可触发) API通知Notifyable(例如侦听器),而Notifyaable只能等待事件。因此,合同体现在设计中。
-
问题: 由于侦听器对事件作出反应而不是声明,因此它需要知道通知它的人。
-
解决方案:
- 每个TriggerHandle都有一个唯一的id,用作“ConditionNotificationer”中的索引。
- 当调用“ConditionNotifier::notify”时,侦听器将通过
NotificationVector_t
从ConditionListener::wait()
返回值索引通知了他。因此,它与TriggerHandle的唯一id相同,侦听器知道哪个Triggerable通知了他。
Event_t
Concurrency(并发)
侦听器必须能够同时附加和分离事件。此外,它支持回调可以附加或分离更多的事件,或者同时分离其相应的事件。此外,Listener支持在附加/分离事件时同时调用回调。
为了实现这一点,我们创建了“Event_t”抽象,它存储在一个名为“m_events”的数组中。如果我们想附加或分离一个事件,我们可以初始化“event_t::init()”或重置“m_events”数组中的相应条目“event_t::reset()”。数组的优点是数据结构本身在运行时不会发生变化,因此它不必是线程安全的。
线程安全性必须由“Event_t”类本身来确保。由于每个并发操作都包含在“Event_t”中,我们可以将“concurrent::smart_lock”与“std::recursive_mutex”组合使用,以确保线程安全访问。
- 并发的附加/分离事件和回调执行,由线程安全的“event_t”确保。
- 通过“std::recursive_mutex”确保将自身从回调中分离。
- 通过用“concurrent::smart_lock”保护“m_events”中的每个“Event_t”对象,可以确保在回调中附加/分离任意事件。如果数据结构本身必须是线程安全的,那么这是不可能的。
Lifetime
由于事件包含处理事件所需的所有内容,因此它有责任确保TriggerHandle的使用寿命。这是通过在“Event_t::reset()”中调用的“m_invalidationCallback”来完成的,以使相应Triggerable中的TriggerHandle无效。当事件分离或侦听器超出范围时,就会执行此操作。
Triggerable (e.g. Subscriber)
Triggerable是一组类,其中的事件可以附加到侦听器。
可以将类的特定事件附加到侦听器,也可以在不提供事件的情况下附加该类。
其基本思想是,每当附加事件时,侦听器都会创建一个TriggerHandle,并将该TriggerHandle提供给相应的Triggerable。Triggerable然后使用TriggerHandle向侦听器通知事件。
Triggerable With Single Event
Every Triggerable requires:
- The private methods:
void enableEvent(iox::popo::TriggerHandle&& triggerHandle) noexcept;
void disableEvent() noexcept;
void invalidateTrigger(const uint64_t uniqueTriggerId) noexcept;
Triggerable With Multiple Events
Every Triggerable requires:
- An
enum class
which usesiox::popo::EventEnumIdentifier
as underlying type.
enum class EventEnum : iox::popo::EventEnumIdentifier {
EVENT_IDENTIFIER,
ANOTHER_EVENT_IDENTIFIER,
};
- The private methods:
void enableEvent(iox::popo::TriggerHandle&& triggerHandle, const EventEnum event) noexcept;
void disableEvent(const EventEnum event) noexcept;
void invalidateTrigger(const uint64_t uniqueTriggerId) noexcept;
侦听器使用上述方法将TriggerHandle的所有权转移到Triggerable。对于每个可连接的事件/状态,Triggerable应该有一个TriggerHandle成员。
然后,TriggerHandle用于通过Triggerable通知监听器某个事件已经发生。
- 它必须是
iox::popo::NotificationAttorney
的朋友。可以提供对以前方法的公共访问,但用户可以调用只应由侦听器使用的方法。