User documentation
Overview
The asyncImpl
module defines generic event-driven mechanisms. It is currently used in the asyncFreeRtos
implementation of the async
module.
It implements Runnable
aspect of async
. Refer to asyncFreeRtos/include/async/TaskContext.h
in asyncFreeRtos
for usage examples.
- Module consists of classes:
async::EventDispatcher
async::EventPolicy
async::RunnableExecutor
async::IRunnable
async::Queue
EventDispatcher
The async::EventDispatcher
allows registering or deregistering event handlers using the async::EventDispatcher::setEventHandler()
and async::EventDispatcher::removeEventHandler()
.
The async::EventDispatcher::handleEvents()
executes handlers based on the provided event bit mask.
The async::TaskContext
from asyncFreeRtos
module is async::EventDispatcher
.
EventPolicy
async::EventPolicy
is a wrapper around async::EventDispatcher
, responsible for managing a specific event.
The main difference between async::EventPolicy
and async::EventDispatcher
lies in the method async::EventPolicy::setEvent()
. This method takes no arguments
and sets the event bit mask to include the event bit that async::EventPolicy
is responsible for.
RunnableExecutor
The async::RunnableExecutor
implements the async::execute()
from async
module in asyncFreeRtos
.
When the async::TaskContext::execute()
is called, the async::RunnableExecutor
places the Runnable in the queue and sets the event it is responsible for.
Once the async::EventDispatcher::handleEvents()
(from async::TaskContext
) is called, the async::EventDispatcher
invokes the async::RunnableExecutor
handler, which executes all enqueued Runnables.
IRunnable
async::IRunnable
is a simple interface with a single method: async::IRunnable::execute()
. For simplicity, both the interface and its implementations will be referred to as runnables.
Queue
The async::Queue
is an implementation of simple queue, used in async::RunnableExecutor
to hold runnable objects.
How asyncImpl
is used in async::TaskContext
The async::asyncImpl
classes are used in async/TaskContext.h
of the asyncFreeRtos
module.
async::TaskContext
acts as a wrapper for a FreeRTOS task and async::EventDispatcher
that handles three events: timer, runnable, and stop events.
The main function of the underlying FreeRTOS task is implemented in async::TaskContext::dispatch()
.
This function blocks itself using FreeRTOS::xTaskNotifyWait()
, waiting for FreeRTOS::xTaskNotify()
to be triggered via async::TaskContext::setEvents()
with an event bit mask (refer to the FreeRTOS documentation for details).
Once unblocked, async::EventDispatcher::handleEvents()
(from async::TaskContext
) is called, where the async::EventDispatcher
invokes handlers for all active events.
When async::TaskContext::setEvents()
is called with a bit mask of 1 << 2, corresponding to the stop event, the execution of async::TaskContext::dispatch()
is terminated.
Correspondingly, bit mask of 1 << 0 sets the timer event and 1 << 1 leads to execution of enqueued Runnables.
Example
The following example shows the sample class with event-driven mechanisms of asyncImpl
.
It could be used as a template for implementation of the async::TaskContext
for the new platforms.
To port async
to a new platform, one must start by providing an adequate Types.h
, mapping the key definitions of the async
to the target platform:
struct ExampleLock
{};
using RunnableType = IRunnable;
using ContextType = uint8_t;
using EventMaskType = uint32_t;
using LockType = ExampleLock;
struct TimeUnit
{
enum Type
{
MICROSECONDS = 1,
MILLISECONDS = 1000,
SECONDS = 1000000
};
};
using TimeUnitType = TimeUnit::Type;
class TimeoutType
{
public:
void cancel();
};
Header of the sample class that handles 3 events: A, B and runnable:
class AsyncImplExample : public async::EventDispatcher<3U, async::LockType>
{
public:
AsyncImplExample();
void setEvents(async::EventMaskType eventMask);
void setEventA();
void setEventB();
void execute(async::RunnableType& runnable);
void dispatch();
private:
void handlerEventA();
void handlerEventB();
void printBitmask(async::EventMaskType const eventMask);
friend class async::EventPolicy<AsyncImplExample, 0U>;
friend class async::EventPolicy<AsyncImplExample, 1U>;
friend class async::EventPolicy<AsyncImplExample, 2U>;
async::EventMaskType _eventMask;
async::EventPolicy<AsyncImplExample, 0U> _eventPolicyA;
async::EventPolicy<AsyncImplExample, 1U> _eventPolicyB;
async::RunnableExecutor<async::RunnableType, async::EventPolicy<AsyncImplExample, 2U>, LockType>
_runnableExecutor;
};
The key points of this implementation are methods AsyncImplExample::setEvents()
and AsyncImplExample::dispatch()
.
In real life scenario, the logic to wakeup a task/context, which calls AsyncImplExample::dispatch()
could be placed
inside AsyncImplExample::setEvents()
and AsyncImplExample::dispatch()
in the main body of task/context:
AsyncImplExample::AsyncImplExample()
: _eventMask(0), _eventPolicyA(*this), _eventPolicyB(*this), _runnableExecutor(*this)
{
_eventPolicyA.setEventHandler(
HandlerFunctionType::create<AsyncImplExample, &AsyncImplExample::handlerEventA>(*this));
_eventPolicyB.setEventHandler(
HandlerFunctionType::create<AsyncImplExample, &AsyncImplExample::handlerEventB>(*this));
_runnableExecutor.init();
}
void AsyncImplExample::printBitmask(async::EventMaskType const eventMask)
{
size_t const bits = 8;
printf("0b");
for (size_t i = bits - 1; i > 0; i--)
{
printf("%d", (eventMask >> (i - 1)) & 1);
}
}
void AsyncImplExample::execute(async::RunnableType& runnable)
{
printf("AsyncImplExample::execute() called, Runnable is prepared for execution\n");
_runnableExecutor.enqueue(runnable);
}
void AsyncImplExample::handlerEventA() { printf("AsyncImplExample::handlerEventA() is called.\n"); }
void AsyncImplExample::handlerEventB() { printf("AsyncImplExample::handlerEventB() is called.\n"); }
void AsyncImplExample::setEventA()
{
printf("AsyncImplExample::setEventA() is called.\n");
_eventPolicyA.setEvent();
}
void AsyncImplExample::setEventB()
{
printf("AsyncImplExample::setEventB() is called.\n");
_eventPolicyB.setEvent();
}
/**
* This method is called everytime an event is set.
* A logic to wakeup a task/context, which calls AsyncImplExample::dispatch() should be placed
* here.
*/
void AsyncImplExample::setEvents(async::EventMaskType const eventMask)
{
_eventMask |= eventMask;
printf("AsyncImplExample::setEvents() is called with eventMask:");
printBitmask(eventMask);
printf(" new eventMask:");
printBitmask(_eventMask);
printf("\n");
}
/**
* This method is calling handlers for active events.
* Should be placed in the main body of task/context.
*/
void AsyncImplExample::dispatch()
{
printf("AsyncImplExample::dispatch() is called, eventMask:");
printBitmask(_eventMask);
printf("\n");
handleEvents(_eventMask);
_eventMask = 0;
printf("AsyncImplExample::dispatch() reset eventMask, eventMask:");
printBitmask(_eventMask);
printf("\n");
}
} // namespace asyncNewPlatform
void exampleRunnableA() { printf("exampleRunnableA is called.\n"); }
void exampleRunnableB() { printf("exampleRunnableB is called.\n"); }
int main()
{
auto eventManager = asyncNewPlatform::AsyncImplExample();
async::Function runnableA(::estd::function<void()>::create<&exampleRunnableA>());
async::Function runnableB(::estd::function<void()>::create<&exampleRunnableB>());
eventManager.setEventA();
eventManager.setEventB();
eventManager.execute(runnableA);
eventManager.execute(runnableB);
eventManager.dispatch();
return 0;
}
This example prints following:
AsyncImplExample::setEventA() is called.
AsyncImplExample::setEvents() is called with eventMask:0b0000001 new eventMask:0b0000001
AsyncImplExample::setEventB() is called.
AsyncImplExample::setEvents() is called with eventMask:0b0000010 new eventMask:0b0000011
AsyncImplExample::execute() called, Runnable is prepared for execution
AsyncImplExample::setEvents() is called with eventMask:0b0000100 new eventMask:0b0000111
AsyncImplExample::execute() called, Runnable is prepared for execution
AsyncImplExample::setEvents() is called with eventMask:0b0000100 new eventMask:0b0000111
AsyncImplExample::dispatch() is called, eventMask:0b0000111
AsyncImplExample::handlerEventA() is called.
AsyncImplExample::handlerEventB() is called.
exampleRunnableA is called.
exampleRunnableB is called.
AsyncImplExample::dispatch() reset eventMask, eventMask:0b0000000