Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
Async Getting Started

This guide is for contributors who are new to Uni20 async code.

Goal: after reading this, you should be able to write a small coroutine kernel, understand why it suspends/resumes, and avoid the most common correctness traps.

What Problem the Runtime Solves

Uni20 async code expresses dependency order directly in dataflow form. You do not manually lock data structures or coordinate condition variables.

Instead:

  • values live in Async<T>
  • read/write access goes through ReadBuffer<T> and WriteBuffer<T>
  • coroutines suspend at co_await until the epoch is ready
  • scheduler controls when suspended tasks resume

Think of each Async<T> as a timeline of versions (epochs): one writer phase, then reader phase, then next epoch.

Minimal Setup

using namespace uni20::async;
The Async<T> container: coroutine‐safe asynchronous read/write.
Concepts and helpers for composing Async expressions.
Coroutine scheduler for AsyncTask.
This represents the reverse-mode adjoint for functions of complex variables, assuming a real-valued s...
Definition async/async.hpp:26

For first experiments and most unit tests, use DebugScheduler. Normal application code can use the default global scheduler without ScopedScheduler.

ScopedScheduler scoped(&sched);
Single-threaded scheduler with configurable runnable-batch ordering.
Definition debug_scheduler.hpp:63
RAII helper that temporarily overrides the global scheduler.
Definition debug_scheduler.hpp:219

Async Expressions (Operator Overloads)

Many operators on Async<T> (and mixes of Async<T> with scalars) build a DAG by scheduling coroutines. They do not compute immediately.

Async<int> a = 2;
Async<int> b = a + 1; // schedules a kernel; does not block
int b_value = b.get_wait(); // blocks until the scheduled work completes
(void)b_value;
Async<T> is a container for coroutine-safe asynchronous read/write.
Definition async/async.hpp:93
T const & get_wait() const
Block the current thread until the value becomes available.
Definition async_impl.hpp:52

Operator kernels schedule work onto the global scheduler. If you need a non-default scheduler, use ScopedScheduler or set_global_scheduler(...). If you only need completion and do not need the value, use b.wait() instead.

First Complete Example

schedule([](WriteBuffer<int> writer) static -> AsyncTask {
co_await writer = 42;
co_return;
}(value.write()));
schedule([](ReadBuffer<int> reader) static -> AsyncTask {
int v = co_await reader;
TRACE("Read value =", v);
co_return;
}(value.read()));
sched.run_all();
Canonical host-oriented coroutine task type.
Definition async_task.hpp:196
void run_all()
Run until no runnable tasks remain.
Definition debug_scheduler.hpp:371
RAII handle for reading the value from an Async container at a given epoch.
Definition buffers.hpp:53
Exclusive mutable-access buffer for one Async<T> epoch.
Definition buffers.hpp:859
locale_t value
Definition data_table.cpp:49
#define TRACE(...)
Definition trace.hpp:89

What happened:

  1. value starts with unconstructed storage.
  2. writer coroutine reaches co_await writer = 42 and constructs the value.
  3. reader coroutine resumes once epoch reaches read phase.
  4. sched.run_all() drains all scheduled work.

First Important Semantic Rule

Default Async<T> does not default-construct T.

First write is safe through proxy assignment when the source can both construct and later assign T:

co_await writer = 42;

If the value already exists, this evaluates stored_value = source. Use explicit emplace(...) when reconstruction rather than assignment is intended.

What is unsafe is forcing mutable-reference style access before construction:

int& out = co_await writer;
out = 42;

This can throw buffer_write_uninitialized.

Typical Read-Compute-Write Kernel

When input and output are different timelines, use a reader for the input and a writer for the output:

auto increment = [](ReadBuffer<int> in, WriteBuffer<int> out) static -> AsyncTask {
auto owned = co_await in.transfer();
int v = owned.get();
owned.release();
co_await out = v + 1;
co_return;
};
void release() noexcept
Manually release the epoch reader before awaitable destruction.
Definition buffers.hpp:143
ReadBuffer && transfer() &noexcept
Transfer this read buffer into the owning-await path.
Definition buffers.hpp:147

Why owned.release() is often useful:

  • it shortens the reader lifetime once the input value has been copied
  • it may allow later writers on the input timeline to begin sooner

transfer() is the explicit owning form for named buffers. Direct co_await on a temporary buffer still uses the owning rvalue path.

In-Place Mutation Uses One Writer

WriteBuffer<T> provides exclusive mutable access; it is not write-only. Use the existing value through the write proxy:

auto increment_in_place = [](WriteBuffer<int> value) static -> AsyncTask {
auto access = co_await value;
access.get() += 1;
co_return;
};

Do not model ordinary mutation by taking both value.read() and value.write(). Those buffers select different epochs and cannot be held as simultaneous read/write access. A deliberate read-then-write transition on one queue is an advanced pattern that must copy the required data and release the reader before awaiting the later writer.

Quick Error Handling

Main buffer access failures:

  • buffer_read_uninitialized
  • buffer_write_uninitialized
  • task_cancelled

Unhandled exceptions thrown inside a coroutine are captured in the promise and forwarded to registered sink epochs (usually output write buffers).

To explicitly route exceptions to additional buffers, use:

co_await propagate_exceptions_to(buffer_a, buffer_b);

Coroutine Style Requirements

For async coroutine lambdas (AsyncTask return type):

  • no captures
  • static lambda call operator

Correct pattern:

auto task = [](ReadBuffer<int> in, WriteBuffer<int> out) static -> AsyncTask {
int v = co_await in;
co_await out = v;
co_return;
};

This avoids closure lifetime issues when coroutine lifetime exceeds lambda object lifetime.

Where to Go Next

Examples to Read

The examples/ folder contains runnable async demos that match current semantics: