This blog post is the second part of the mini-series on a task scheduler and builds on the previous article “Software Development: A Compact Introduction to Coroutines by Dian-Lun Li”.
Advertisement
A single-threaded scheduler for C++ coroutines
In this section, I implement a single-threaded scheduler to schedule coroutines. Let’s start with the interface:
Task TaskA(Scheduler& sch) {
std::cout Both TaskA and TaskB are coroutines. In the main function I construct a scheduler and place the two tasks (coroutine handles) into the scheduler. Then I call schedule to schedule the two tasks. A Task is a Coroutine object defined as follows:
struct Task {
struct promise_type {
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
Task get_return_object() {
return std::coroutine_handle
}
void return_void() {}
void unhandled_exception() {}
};
Task(std::coroutine_handle
auto get_handle() { return handle; }
std::coroutine_handle
};
One thing to note is that I return std::suspend_always in both the initial_suspend and final_suspend functions. This is necessary because I want to hand over all coroutine execution to the scheduler. Coroutines don’t run until I call schedule. The scheduler is defined as follows:
class Scheduler {
//std::queue<:coroutine_handle>> _tasks;
std::stack<:coroutine_handle>> _tasks;
public:
void emplace(std::coroutine_handle task) {
_tasks.push(task);
}
void schedule() {
while(!_tasks.empty()) {
//auto task = _tasks.front();
auto task = _tasks.top();
_tasks.pop();
task.resume();
if(!task.done()) {
_tasks.push(task);
}
else {
task.destroy();
}
}
}
auto suspend() {
return std::suspend_always{};
}
};
In the scheduler, I store tasks in a stack and implement the emplace member function so that users can push a task onto the stack. In the schedule member function I keep removing a task from the stack. When I resume a task, I check whether the task is completed. If not, I push the task back onto the stack to schedule it later. Otherwise I will destroy the finished task. After the program runs, the following results are available:
The scheduler uses a stack (last in, first out) to store tasks. If I replace the stack with a queue (first in, first out), the execution order of the tasks changes:
For the sake of completeness, both programs are summarized here again:
// stackScheduler.cpp
#include
#include
#include
struct Task {
struct promise_type {
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
Task get_return_object() {
return std::coroutine_handle
}
void return_void() {}
void unhandled_exception() {}
};
Task(std::coroutine_handle
auto get_handle() { return handle; }
std::coroutine_handle
};
class Scheduler {
std::stack<:coroutine_handle>> _tasks;
public:
void emplace(std::coroutine_handle task) {
_tasks.push(task);
}
void schedule() {
while(!_tasks.empty()) {
auto task = _tasks.top();
_tasks.pop();
task.resume();
if(!task.done()) {
_tasks.push(task);
}
else {
task.destroy();
}
}
}
auto suspend() {
return std::suspend_always{};
}
};
Task TaskA(Scheduler& sch) {
std::cout
#include
#include
struct Task {
struct promise_type {
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
Task get_return_object() {
return std::coroutine_handle
}
void return_void() {}
void unhandled_exception() {}
};
Task(std::coroutine_handle
auto get_handle() { return handle; }
std::coroutine_handle
};
class Scheduler {
std::queue<:coroutine_handle>> _tasks;
public:
void emplace(std::coroutine_handle task) {
_tasks.push(task);
}
void schedule() {
while(!_tasks.empty()) {
auto task = _tasks.front();
_tasks.pop();
task.resume();
if(!task.done()) {
_tasks.push(task);
}
else {
task.destroy();
}
}
}
auto suspend() {
return std::suspend_always{};
}
};
Task TaskA(Scheduler& sch) {
std::cout What’s next?
This article by Dian-Lun Li shows a simple scheduler for coroutines. I’ll use Dian-Lun’s scheduler in my next post for further experiments. (map)
To home page