Awaitable Customizations#

TMC awaitables expose several customization functions that allow you to control where an awaitable or child task runs and how the awaiting task is resumed.

First, you may call any or all of run_on(), resume_on(), or with_priority().

Then, you must call exactly one of co_await, fork(), or detach() to submit the task for execution.

Not every awaitable supports every customization, but the behavior of each customization is the same across all awaitables that implement it.

Optional Customizations (Choose Any):#

Customizations can be applied individually…

co_await tmc::spawn(some_task()).with_priority(2);
co_await tmc::spawn(some_io_task()).run_on(tmc::asio_executor());

… or chained together fluently.

auto result = co_await tmc::spawn(some_task()).run_on(tmc::cpu_executor()).with_priority(1).resume_on(tmc::asio_executor());

.run_on()#

Specifies the executor on which the awaitable or child task will run. Does not affect the executor on which the awaiting task will resume.

If you do not call this customization function, the default behavior is equivalent to .run_on(tmc::current_executor()).

.resume_on()#

Specifies the executor on which the current task will be resumed after the awaitable completes. Does not affect the executor on which the awaitable or child task will run.

If you do not call this customization function, the default behavior is equivalent to .resume_on(tmc::current_executor()).

.with_priority()#

Specifies the priority at which the awaitable or child task will run. Does not affect the priority at which the awaiting task will resume.

If you do not call this customization function, the default behavior is equivalent to .with_priority(tmc::current_priority()).

Execution Modes (Choose One):#

co_await#

Immediately suspends the current task and executes the awaitable or child task.

auto result = co_await tmc::spawn(task_result());

.fork()#

By default, TMC awaitables are “lazy” and do not begin executing until they are awaited. Call .fork() to make the awaitable “eager” by initiating it immediately, and allowing you to defer awaiting until later.

However, you must await the result before the awaitable type goes out of scope. Failure to do so will cause undefined behavior.

// OK
 auto eager_task = tmc::spawn(task_void()).fork();
 do_some_work();
 co_await std::move(eager_task);

// Not OK. The nasal demons will appear.
{
  auto eager_task = tmc::spawn(task_void()).fork();
  do_some_work();
  return;
}

.detach()#

Call .detach() to begin executing an awaitable immediately and never await it. This is only implemented for void-returning tasks.

tmc::spawn(task_void()).detach();

Advanced Usage Example#

By combining these customizations together, you can achieve some complex behavior.

// Suspend the current task and save the current executor and current priority.
// Run task_result() on another executor at priority 1.
// When it completes, resume the current (awaiting) task back on the current (saved) executor at the current (saved) priority.
auto result = co_await tmc::spawn(task_result()).run_on(another_executor).with_priority(1);

// Spawn a low-priority background task that runs on the CPU executor.
tmc::spawn(background_task()).run_on(tmc::cpu_executor()).with_priority(5).detach();

// Start task_gpu on gpu_executor.
// Do some more work in the current coroutine before awaiting it.
// Await it later, AND when it completes, resume the current (awaiting) task on the asio_executor.
auto hot_gpu_task = tmc::spawn(task_gpu()).run_on(gpu_executor).resume_on(tmc::asio_executor()).fork();
do_some_cpu_bound_work();
co_await std::move(hot_gpu_task);
// We are now on tmc::asio_executor(). Send some data over the IO.
// When that completes, resume back on the CPU executor.
co_await tmc::spawn(send_response()).resume_on(tmc::cpu_executor());