Coroutines: sleep_for() challenge
Reflection on my recent online talk
Introduction
Coroutines are not a novelty, and they are definitely not a C++-specific language feature.
Many other languages—including C#, Kotlin, and JavaScript, to name just a few—also support coroutines, with the same underlying meaning: they represent an execution unit (a task) rather than an execution context. Before coroutines, in the traditional thread-centric approach to concurrency, the execution unit was the thread function. As a result, the execution unit and the execution context were effectively the same.
The coroutines we know in C++ are standardized by the group led by the recently passed away Mr. Gor Nishanov (1971-2026), in form of the <coroutine> header file – with rudimental support.
They are part of all major families of compilers, but there is no standardized coroutine framework. There are only third-party libraries.
I’ve recently had an online talk where I’ve demonstrated the coroutines in action, using my socket library as application field.
The message to audience, which I’ve tried to broadcast, is that even if we have the coroutines framework, employing the coroutines as a direct replacement for the existing code is usually an antipattern. The fact is – the entire previous solution – a classical implementation for the receiver component, as the common for both socket endpoints (Server and Client), had to be redesigned to be coroutine-friendly, especially since coroutines are like the Borgs from Star Trek – they turn everything they touch, their surrounding environment – into coroutines (Figure-1).
Switching from threads to coroutines and structured concurrency, the coroutine framework needs to redefine the synchronization primitives as well. The std synchronization primitives are expensive OS concepts used for synchronizing the threads, as execution context that is at the same time execution unit. With coroutines in place, we have multiple execution units that may be hosted by the same execution context – the thread (CPU/GPU single-thread, pool of threads, etc.). The same is for std::this_thread::sleep_for(), as the way to suspend – block the current thread for a given amount of time. What we want is to reimplement the same mechanism – but in the scope of a single coroutine, suspending the single coroutine for a given time interval, while allowing another coroutine hosted by the same thread to advance – to make a progress.
Figure-1. Coroutines as the Borg: A Star Trek Analogy (AI generated image)
Asynchronous sleep_for
The goal that we try to achieve here – is to block a single coroutine, rather than entire thread, for a given amount of time. Eventually, we want to be able to call inside the coroutine
namespace details
{
template <typename Duration>
struct sleep_for_awaitable final : std::suspend_always
{
private:
Duration timeout_;
utils::coro::TaskResumeContext* context_{nullptr};
public:
template <typename D>
requires std::convertible_to<D, Duration>
explicit sleep_for_awaitable(D&& timeout,
utils::coro::TaskResumeContext* context) noexcept
: timeout_{std::forward<Duration>(timeout)}
, context_{context}
{
}
// the rest of code
};
}skipping for time being the implementation itself, we can write the helper method
namespace details
{
template <typename Duration>
[[nodisacard]] auto sleep_for(Duration&& timeout,
utils::coro::TaskResumeContext* context = nullptr) noexcept
{
return sleep_for_awaitable<Duration>{std::forward<Duration>(timeout), context};
}
}
that returns our awaitable interface implementation.
utils::coro is namespace from my small coroutine framework.
The TaskResumeContext is the way to specify explicitly the context on which the suspended caller should be resumed – after timeout being expired.
For the rest of the article, along with full implementation - please visit my blog


