Home » Coroutines: A Scheduler for Tasks – Part 2 by Dian-Lun Li

Coroutines: A Scheduler for Tasks – Part 2 by Dian-Lun Li

by admin
Coroutines: A Scheduler for Tasks – Part 2 by Dian-Lun Li

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

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::from_promise(*this);
}
void return_void() {}
void unhandled_exception() {}
};

Task(std::coroutine_handle handle): handle{handle} {}

auto get_handle() { return handle; }

std::coroutine_handle 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:

See also  Time in C++20: Introduction to Chrono Terminology

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::from_promise(*this);
}
void return_void() {}
void unhandled_exception() {}
};

Task(std::coroutine_handle handle): handle{handle} {}

auto get_handle() { return handle; }

std::coroutine_handle 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::from_promise(*this);
}
void return_void() {}
void unhandled_exception() {}
};

Task(std::coroutine_handle handle): handle{handle} {}

auto get_handle() { return handle; }

std::coroutine_handle 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

You may also like

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More

Privacy & Cookies Policy