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::EventDispatcherasync::EventPolicyasync::RunnableExecutorasync::IRunnableasync::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(::etl::delegate<void()>::create<&exampleRunnableA>());
async::Function runnableB(::etl::delegate<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