Quick Start
The async module lets C++20 coroutines suspend while they wait for timers, network operations, signals, or work running on another thread.
A small Vix Async program normally has three parts: an io_context, one or more task<T> coroutines, and a call to run() that drives the scheduler.
Header
Use the public Vix header:
#include <vix/async.hpp>For examples that print output:
#include <vix/print.hpp>Create a first async program
Create main.cpp:
#include <chrono>
#include <vix/async.hpp>
#include <vix/print.hpp>
using namespace std::chrono_literals;
vix::async::task<void> run(vix::async::io_context& ctx)
{
vix::print("waiting");
co_await ctx.timers().sleep_for(100ms);
vix::print("done");
}
int main()
{
vix::async::io_context ctx;
std::move(run(ctx)).start(ctx.get_scheduler());
ctx.run();
return 0;
}The task prints waiting, suspends while the timer is active, then resumes and prints done.
The important part is that the timer does not block the scheduler thread while the coroutine is suspended.
Create and start a task
A task<T> is lazy. Calling a coroutine function creates the task, but does not immediately run its body.
vix::async::task<int> compute()
{
co_return 42;
}To run a root task, start it on a scheduler:
vix::async::io_context ctx;
auto task = compute();
std::move(task).start(ctx.get_scheduler());
ctx.run();Tasks are move-only, so start() is normally called on a moved task.
Inside another coroutine, use co_await instead:
vix::async::task<int> compute()
{
co_return 42;
}
vix::async::task<void> run()
{
int value = co_await compute();
vix::print("value:", value);
}Wait for a timer
Use the timer service when a coroutine needs to wait for time to pass.
#include <chrono>
#include <vix/async.hpp>
#include <vix/print.hpp>
using namespace std::chrono_literals;
vix::async::task<void> run(vix::async::io_context& ctx)
{
vix::print("before");
co_await ctx.timers().sleep_for(250ms);
vix::print("after");
}While this task is suspended, the scheduler can continue running other ready work.
Run several tasks
Tasks can be composed with when_all.
#include <chrono>
#include <vix/async.hpp>
#include <vix/print.hpp>
using namespace std::chrono_literals;
vix::async::task<int> first(vix::async::io_context& ctx)
{
co_await ctx.timers().sleep_for(50ms);
co_return 10;
}
vix::async::task<int> second(vix::async::io_context& ctx)
{
co_await ctx.timers().sleep_for(100ms);
co_return 20;
}
vix::async::task<void> run(vix::async::io_context& ctx)
{
auto results = co_await vix::async::when_all(
ctx.get_scheduler(),
first(ctx),
second(ctx)
);
vix::print("first:", std::get<0>(results));
vix::print("second:", std::get<1>(results));
}when_all starts the supplied tasks and resumes when all of them have completed.
Use when_any when only the first completion matters. The other tasks continue running after when_any returns, so the runtime they use must remain alive until they can finish.
Move CPU work off the scheduler
CPU-intensive or blocking work should not occupy the scheduler thread for a long time.
Use the context CPU pool:
#include <vix/async.hpp>
#include <vix/print.hpp>
vix::async::task<void> run(vix::async::io_context& ctx)
{
int result = co_await ctx.cpu_pool().submit([](){
int total = 0;
for (int i = 0; i < 100000; ++i)
{
total += i;
}
return total;
});
vix::print("result:", result);
}The callable executes on a worker thread. The coroutine suspends while the work runs, then continues with the returned value.
If no coroutine result is needed, use post() instead.
bool accepted = ctx.cpu_pool().post([](){
vix::print("background work");
});
vix::print("accepted:", accepted);Cancel a supported operation
Cancellation is cooperative.
Create a source and pass its token to an operation that supports cancellation:
vix::async::cancel_source source;
auto token = source.token();Cancellation can then be requested from another part of the program:
source.cancel();The operation decides how to react at its cancellation boundary. Cancellation does not forcibly interrupt arbitrary C++ code that is already executing.
Timers, signals, and supported network operations participate in this model.
Use networking
Networking is also exposed through io_context.
auto& net = ctx.net();The network service provides asynchronous TCP, UDP, and DNS operations.
The normal flow remains the same:
start operation
↓
coroutine suspends
↓
network work completes
↓
continuation returns to scheduler
↓
coroutine resumesThe focused networking pages cover sockets, acceptors, datagrams, resolution, cancellation, and shutdown behavior.
Runtime lifetime
The io_context should remain alive while tasks and services that depend on it are still active.
A typical program follows this order:
create io_context
↓
create root task
↓
start task
↓
run scheduler
↓
finish asynchronous work
↓
shutdownstop() and shutdown() are not the same operation. stop() requests scheduler termination. shutdown() coordinates the services owned by the context before the runtime finishes.
The full lifecycle is covered in Lifecycle and Shutdown.
Next step
Continue with Core Concepts to understand how task<T>, scheduler, and io_context fit together.
Then move to the focused pages for: