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