Skip to content

Async events

Dave Glover edited this page May 26, 2022 · 22 revisions

Async Events

Async events enable you to marshal calls from one thread to another for event-driven applications. This is essential for multithreaded apps that interact with Event Loop functions. An event loop is one of several scenarios where you may need to marshal a call from a thread to the main thread.

All Event Loop functions (except the EventLoop_Stop function) must be called from the same thread the Event Loop is created on, by default the main thread. Failing to do so, will create undefined, frustratingly difficult to diagnose bugs in your code. You have been warned :)

Example

async_example

Binding

typedef struct _asyncBinding {
    char *name;
    bool triggered;
    void *data;
    void (*handler)(struct _asyncBinding *handle);
} DX_ASYNC_BINDING;

Binding functions

void dx_asyncInit(DX_ASYNC_BINDING *async);
void dx_asyncSend(DX_ASYNC_BINDING *binding, void *data);
void dx_asyncSetInit(DX_ASYNC_BINDING *asyncSet[], size_t asyncCount);
void dx_asyncRunEvents(void);

binding handler

void name(DX_ASYNC_BINDING *handle)

Macros

#define DX_ASYNC_HANDLER(name, handle)  \
    void name(DX_ASYNC_BINDING *handle) \
    {

#define DX_ASYNC_HANDLER_END }

#define DX_DECLARE_ASYNC_HANDLER(name) void name(DX_ASYNC_BINDING *handle)

Usage

Declare async bindings

static DX_ASYNC_BINDING async_test = {.name = "async_test", .handler = async_test_handler};
static DX_ASYNC_BINDING async_test2 = {.name = "async_test2", .handler = async_test2_handler};

Declare an async binding set

DX_ASYNC_BINDING *asyncSet[] = {&async_test, &async_test2};

Initialize the async binding set

dx_asyncSetInit(asyncSet, NELEMS(asyncSet));

Implement your async binding callback

Note, due to the multithreaded locking strategy, you cannot call the dx_asyncSend function from an async binding callback function. Calling dx_asyncSend from an async callback function will result in a deadlock.

DX_ASYNC_HANDLER(async_test_handler, handle)
{
    // Implement task to be run on the main thread.

    int value = *((int *)handle->data);
    Log_Debug("Data1:%d\n", value);

    // The async event will start a oneshot timer on the same thread as the event loop
    dx_timerOneShotSet(&tmr_led, &(struct timespec){0,1});
}
DX_ASYNC_HANDLER_END

Initiate an async event

Create a thread, and from the thread call dx_asyncSend to trigger the associated async binding callback function. The async binding callback function will be called after pending event loop events have completed. The async binding callback function will run on the event loop thread, by default, the main thread.

static void *count_thread(void *arg)
{
    int count = 0;

    while (true) {
        count++;
        dx_asyncSend(&async_test, (void *)&count);
        nanosleep(&(struct timespec){0, 20 * ONE_MS}, NULL);
    }
    return NULL;
}

Extended Event Loop Run

The standard EventLoopRun pattern has been extended to check for pending async events. If there are pending async events, then dx_asyncRunEvents is called to run the pending async binding callback functions.

The

while (!terminationRequired) 
{
    if (asyncEventReady) {
        dx_asyncRunEvents();
    }

    int result = EventLoop_Run(dx_timerGetEventLoop(), -1, true);
    // Continue if interrupted by signal, e.g. due to breakpoint being set.
    if (result == -1 && errno != EINTR) {
        dx_terminate(DX_ExitCode_Main_EventLoopFail);
    }
}
Clone this wiki locally