User documentation

Overview

The async module defines an interface for asynchronous execution of runnable objects in specific execution contexts.

To be able to run code on arbitrary platforms in a generic way, this module provides an abstraction on top of OS specific primitives. async uses the underlying async::AsyncBinding type to access to specific OS implementations, for instance via async::FreeRtosAdapter for FreeRTOS OS.

Features

Three asynchronous (non-blocking) functions are declared by async in Async.h:

execute

Allows to execute a async::RunnableType immediately in an execution async::ContextType

schedule

Allows to schedule a async::RunnableType to be run after a delay in a async::ContextType

scheduleAtFixedRate

Allows to schedule a async::RunnableType to be run periodically in a async::ContextType

The util/Call.h declares a runnable class that allows customized implementation on execution by providing callable object (with function call operator ()). For example the predefined async::Function type is declaring ::estd::function as callable type.

The util/MemberCall.h provides a async::MemberCall class, which allows to create a async::RunnableType from a member function of a non-runnable class.

Examples

Making runnable class and scheduling it

Define a class that implements the async::IRunnable interface:

#include <async/Async.h>

class RunnableImpl : public ::async::IRunnable
{
public:
    void execute() override
    {
        fprintf(stdout, "Executing user logic!\n");
    }
};

In client code allocate the runnable object and asynchronously execute it:

static RunnableImpl runnable;
static ::async::TimeoutType timeout;

void executeRunnable(::async::ContextType& context)
{
    // asynchronously execute the runnable:
    ::async::execute(context, runnable);

    // schedule the runnable for single-time execution after 1 second from now:
    ::async::schedule(context, runnable, timeout, 1, ::async::TimeUnit::SECONDS);

    // schedule the runnable for cyclic execution with period 2 seconds:
    ::async::scheduleAtFixedRate(context, runnable, timeout, 2, ::async::TimeUnit::SECONDS);
}

Use async::Function to provide customized implementation on execution

Allocate the timeout variables and define execution callback functions:

static constexpr ::async::TimeUnitType PERIOD_IN_S = 1;
static ::async::TimeoutType runTimeout;
static ::async::TimeoutType cancelTimeout;

void runModule()
{
    fprintf(stdout, "running!\n");
}

void stopModule()
{
    runTimeout.cancel();
    fprintf(stdout, "stopped!\n");
}

In client code specify the async::Function objects and schedule them:

void startStopModule(::async::ContextType& context)
{
    // execute runModule every PERIOD_IN_S:
    ::async::Function moduleRunnable(runModule);
    ::async::scheduleAtFixedRate(context, moduleRunnable, runTimeout, PERIOD_IN_S, ::async::TimeUnit::SECONDS);

    // execute stopModule after 2*PERIOD_IN_S:
    ::async::Function cancelRunnable(stopModule);
    ::async::schedule(context, cancelRunnable, cancelTimeout, 2*PERIOD_IN_S, ::async::TimeUnit::SECONDS);
}

This example could produce the following output, if startStopModule() is called:

running!
running!
stopped!

Use async::MemberCall to force non-runnable class behave like runnable

Declare a non-runnable class:

#include <async/Async.h>
#include <async/util/MemberCall.h>

class NonRunnable // the class is NOT inheriting from Runnable!
{
public:
    void run()
    {
        fprintf(stderr, "running!\n");
    }
    // must not necessarily be member of the class:
    ::async::MemberCall<NonRunnable> memberCall{this, &NonRunnable::run};
};

In client code, use the async::MemberCall object to schedule the execution:

void executeNonRunnable(NonRunnable & nonRunnable, ::async::ContextType& context)
{
    // asynchronously call the run method of the NonRunnable object:
    ::async::execute(nonRunnable.memberCall, 0);
    // asynchronously call the run method with delay 1 second:
    ::async::schedule(context, nonRunnable.memberCall, timeout, 1, ::async::TimeUnit::SECONDS);
    // asynchronously call the run method periodically every 2 seconds:
    ::async::scheduleAtFixedRate(context, nonRunnable.memberCall, timeout, 2, ::async::TimeUnit::SECONDS);
}

Relevant types

Context

async::ContextType represents an execution context. All functions that run in the same context are guaranteed to be run sequentially, allowing safe access to shared resources.

It can be created from a uint8_t and is copyable, assignable, and comparable, with a defined invalid value (CONTEXT_INVALID).

Runnable

async::RunnableType is an object that defines a void execute(void) method, allowing it to be executed. The async::IRunnable interface in async::AsyncImpl is one example of a async::RunnableType, used by certain implementations.

Locks

async::LockType and async::ModifiableLockType are scoped locks. The modifiable counterpart can be unlocked and locked manually.

Warning

The funcional and non-functional semantics of these lock types can differ between implementations of this module for different target platforms. In some cases, platform specific usage invariants may apply. Using these lock types will impact the software’s real-time performance and should only be employed when absolutely necessary and with a clear understanding of their broader impact.

Time units

async::TimeUnitType is an alias of TimeUnit type providing a definition of micro-, milli-, and full seconds. The following values are available within the async::TimeUnit scope:

TimeUnit::MICROSECONDS
TimeUnit::MILLISECONDS
TimeUnit::SECONDS

Timeout

async::TimeoutType provides the necessary memory to support the use of schedule() and scheduleAtFixedRate(). It can be cancelled using its cancel() method.

Integration

If a module uses async, it must verify the correct usage of types by writing a unit test with the async mocks provided in the mock/gmock subfolder.

The following mocks are available, each of which mocks all async methods:

  • AsyncMock

  • LockMock

  • TimeoutMock

When mocking all of async is not desired, TestContext.h and .cpp can be used. They provide a way to emulate async behaviour, that can be triggered manually for a specific context.

Porting to a new platform

To port async to a new platform, one must start by providing corresponding AdapterType, mapping the concepts to the target platform.