Stackless Coroutines
Stackless Coroutines
Libdex supports two coroutine-like execution models:
DexFiberfor stackful execution with its own stack.DexCoroutinefor stackless execution using an explicit state machine.
DexCoroutine is a DexFuture that runs on a DexScheduler.
Use it when you want linear async-style flow without paying for a full fiber stack.
When To Use This Over a Fiber
Use a coroutine when all of these are true:
- The task is mostly “then/await-like” and spends most of its time waiting for futures.
- You want low-overhead scheduling and smaller per-task memory footprint.
- You are comfortable with explicit resume-state style code.
Prefer a DexFiber when:
- You need deep call stacks, existing recursive control flow, or third-party functions that expect normal stackful execution.
How to Run A Coroutine
Use dex_scheduler_spawn_coroutine() to schedule a coroutine.
DexFuture *
my_coroutine (DexCoroutineContext *context,
gpointer user_data)
{
DEX_COROUTINE_BEGIN (context);
/* return a future when done */
return dex_future_new_true ();
DEX_COROUTINE_END;
}
DexFuture *future =
dex_scheduler_spawn_coroutine (NULL,
my_coroutine,
user_data,
user_data_destroy);
If scheduler is NULL, the default scheduler is used.
The Function Contract
Coroutine entrypoints use DexCoroutineFunc:
- Return a
DexFutureto complete. - Return
NULLonly to indicate that the coroutine is suspended. - Store any state you need in the provided
user_data.
The recommended pattern is:
- define any state as a small struct (or with
DEX_DEFINE_CLOSURE_TYPE); - call
DEX_COROUTINE_BEGINat the start; - call
DEX_COROUTINE_SUSPENDwhen you only need to wait for completion; - call
DEX_COROUTINE_SUSPEND_*for each awaited step; - return a future at completion;
- end with
DEX_COROUTINE_END.
Waiting on Futures Correctly
Use DEX_COROUTINE_SUSPEND_BOOLEAN and related macros instead of calling
dex_await_*() directly.
dex_await_*() is a fiber-first API and in practice cannot be used like normal
code unless the future is already done. Coroutine suspend helpers:
- save the awaited future in the coroutine state;
- return
NULLto yield; - resume execution only after that future settles;
- extract the awaited value on resume.
Use DEX_COROUTINE_SUSPEND when you only need to wait for a future to
finish and do not need its result. This is the coroutine-friendly replacement for dex_await (future, NULL).
These macros are available for common await types:
DEX_COROUTINE_SUSPENDDEX_COROUTINE_SUSPEND_BOOLEANDEX_COROUTINE_SUSPEND_INT,DEX_COROUTINE_SUSPEND_UINTDEX_COROUTINE_SUSPEND_INT64,DEX_COROUTINE_SUSPEND_UINT64DEX_COROUTINE_SUSPEND_OBJECT,DEX_COROUTINE_SUSPEND_BOXEDDEX_COROUTINE_SUSPEND_POINTERDEX_COROUTINE_SUSPEND_ENUM,DEX_COROUTINE_SUSPEND_FLAGSDEX_COROUTINE_SUSPEND_DOUBLE,DEX_COROUTINE_SUSPEND_FLOAT
Common Correctness Pattern
Keep coroutine state explicit.
DEX_COROUTINE_SUSPEND_* assumes the state machine stores both the continuation
label and the currently pending future. A typical helper is:
DEX_DEFINE_CLOSURE_TYPE (LoadState, load_state,
DEX_DEFINE_CLOSURE_OBJECT (GFile, file),
DEX_DEFINE_CLOSURE_OBJECT (GFileInputStream, input),
DEX_DEFINE_CLOSURE_OBJECT (GFileInfo, info),
DEX_DEFINE_CLOSURE_VALUE (int, io_priority))
static DexFuture *
load_with_cache (DexCoroutineContext *context,
gpointer user_data)
{
LoadState *state = user_data;
g_autoptr(GError) error = NULL;
DEX_COROUTINE_BEGIN (context);
DEX_COROUTINE_SUSPEND_OBJECT (&state->input, &error,
dex_file_read (state->file, state->io_priority));
if (error != NULL)
return dex_future_new_for_error (g_steal_pointer (&error));
DEX_COROUTINE_SUSPEND_OBJECT (&state->info, &error,
dex_file_input_stream_query_info (state->input,
G_FILE_ATTRIBUTE_STANDARD_SIZE,
state->io_priority));
if (error != NULL)
return dex_future_new_for_error (g_steal_pointer (&error));
return dex_future_new_int64 (g_file_info_get_size (state->info));
DEX_COROUTINE_END;
}
Use DEX_DEFINE_CLOSURE_VALUE_WITH_CLEAR() for inline value types that need
address-based cleanup, such as GWeakRef:
DEX_DEFINE_CLOSURE_TYPE (WatchState, watch_state,
DEX_DEFINE_CLOSURE_VALUE_WITH_CLEAR (GWeakRef,
widget_ref,
g_weak_ref_clear))
Cleanup And Cancellation
Like all DexFuture values, coroutines follow normal ownership and cancellation rules.
- If a coroutine is no longer depended on, discarding may cancel its work.
- Cancellation will propagate into an error for suspended coroutines which they can handle when they are next resumed.
- If work must complete even after callers drop references, use
dex_future_disown()on the returned future. - State registered with the coroutine is released with the configured
callback function when the
DexCoroutinecompletes.
When Not To Use
Do not use coroutines for arbitrary synchronous or blocking loops.
Blocking work should be offloaded with dex_thread_spawn() or DexThreadPool.