Automatically Resuming a Job with Coroutines on a Separate Thread

Contents[Show]

In my last post "Starting Jobs with Coroutines", I applied co_await to start a job. In this post, I improve the workflow and automatically resume a job if necessary. In my final step, I resume the job on a separate thread.

 

TimelineCpp20

This is my 7th post in my mini-series about the new keywords co_return, co_yield, and co_await. To understand this practical introduction to coroutines, you should know all the previous posts:

co_return:

co_yield:

co_await:

Automatically Resuming the Awaiter

In the previous workflow (see Starting Jobs with Coroutines), I presented the awaiter workflow in detail and I explicitly started the job.

int main() {

    std::cout <<  "Before job" << '\n';

    auto job = prepareJob();
    job.start();

    std::cout <<  "After job" <<  '\n';

}

 

This explicit invoking of job.start() was necessary because await_ready in the Awaitable MySuspendAlways always returned false. Now let's assume that await_ready can return true or false and the job is not explicitly started. A short reminder: When await_ready returns true, the function await_resume is directly invoked but not await_suspend.

 

// startJobWithAutomaticResumption.cpp

#include <coroutine>
#include <functional>
#include <iostream>
#include <random>

std::random_device seed;
auto gen = std::bind_front(std::uniform_int_distribution<>(0,1),         // (1)
                           std::default_random_engine(seed()));

struct MySuspendAlways {                                                 // (3)
    bool await_ready() const noexcept { 
        std::cout << "        MySuspendAlways::await_ready"  << '\n';
        return gen();
    }
    bool await_suspend(std::coroutine_handle<> handle) const noexcept {  // (5)
        std::cout << "        MySuspendAlways::await_suspend"  << '\n';
        handle.resume();                                                 // (6)
        return true;

    }
    void await_resume() const noexcept {                                 // (4)
        std::cout << "        MySuspendAlways::await_resume"  << '\n';
    }
};
 
struct Job { 
    struct promise_type;
    using handle_type = std::coroutine_handle<promise_type>;
    handle_type coro;
    Job(handle_type h): coro(h){}
    ~Job() {
        if ( coro ) coro.destroy();
    }

    struct promise_type {
        auto get_return_object() { 
            return Job{handle_type::from_promise(*this)};
        }
        MySuspendAlways initial_suspend() {                     // (2)
            std::cout << "    Job prepared" << '\n';
            return {}; 
        }
        std::suspend_always final_suspend() noexcept {
            std::cout << "    Job finished" << '\n'; 
            return {}; 
        }
        void return_void() {}
        void unhandled_exception() {}
    
    };
};
 
Job performJob() {
    co_await std::suspend_never();
}
 
int main() {

    std::cout <<  "Before jobs" << '\n';

    performJob();
    performJob();
    performJob();
    performJob();

    std::cout <<  "After jobs" <<  '\n';

}

 

First, the coroutine is now called performJob and runs automatically. gen (line 1) is a random number generator for the numbers 0 or 1. It uses for its job the default random engine, initialized with the seed. Thanks to std::bind_front, I can bind it together with the std::uniform_int_distribution to get a callable which, when used, gives me a random number 0 or 1.

A callable is something that behaves like a function. Not only are these named functions but also function objects or lambda expressions. Read more about the new function std::bind_front in the post "More and More Utilities in C++20".

I removed in this example the awaitables with predefined Awaitables from the C++ standard, except the awaitable MySuspendAlways as the return type of the member function initial_suspend (line 2). await_ready (line 3) returns a boolean. When the boolean is true, the control flow jumps directly to the member function await_resume (line 4), when false, the coroutine is immediately suspended and, therefore, the function await_suspend runs (line 5). The function await_suspend gets the handle to the coroutine and uses it to resume the coroutine (line 6). Instead of returning the value true, await_suspend can also return void.

The following screenshot shows: When await_ready returns true, the function await_resume is called, when await_ready returns false, the function await_suspend is also called.

You can try out the program on the Compiler Explorer.
 
startJobWithAutomaticResumption
 
Let me now make the final step and automatically resume the awaiter on a separate thread.

Automatically Resuming the Awaiter on a Separate Thread

The following program is based on the previous program.

 
// startJobWithAutomaticResumptionOnThread.cpp

#include <coroutine>
#include <functional>
#include <iostream>
#include <random>
#include <thread>
#include <vector>

std::random_device seed;
auto gen = std::bind_front(std::uniform_int_distribution<>(0,1), 
                           std::default_random_engine(seed()));
 
struct MyAwaitable {
    std::jthread& outerThread;
    bool await_ready() const noexcept {                    
        auto res = gen();
        if (res) std::cout << " (executed)" << '\n';
        else std::cout << " (suspended)" << '\n';
        return res;                                        // (6)   
    }
    void await_suspend(std::coroutine_handle<> h) {        // (7)
        outerThread = std::jthread([h] { h.resume(); });   // (8)
    }
    void await_resume() {}
};

 
struct Job{
    static inline int JobCounter{1};
    Job() {
        ++JobCounter;
    }
    
    struct promise_type {
        int JobNumber{JobCounter};
        Job get_return_object() { return {}; }
        std::suspend_never initial_suspend() {         // (2)
            std::cout << "    Job " << JobNumber << " prepared on thread " 
                      << std::this_thread::get_id();
            return {}; 
        }
        std::suspend_never final_suspend() noexcept {  // (3)
            std::cout << "    Job " << JobNumber << " finished on thread " 
                      << std::this_thread::get_id() << '\n';
            return {}; 
        }
        void return_void() {}
        void unhandled_exception() { }
    };
};
 
Job performJob(std::jthread& out) {
    co_await MyAwaitable{out};                        // (1)
}
 
int main() {

    std::vector<std::jthread> threads(8);             // (4)
    for (auto& thr: threads) performJob(thr);         // (5)

}

 

The main difference with the previous program is the new awaitable MyAwaitable, used in the coroutine performJob (line 1). On the contrary, the coroutine object returned from the coroutine performJob is straightforward. Essentially, its member functions initial_suspend (line 2) and final_suspend (line 3) return the predefined awaitable std::suspend_never. Additionally, both functions show the JobNumber of the executed job and the thread ID on which it runs. The screenshot shows which coroutine runs immediately and which one is suspended. Thanks to the thread id, you can observe that suspended coroutines are resumed on a different thread.

You can try out the program on the Wandbox.
 
startJobWithAutomaticResumptionOnThread
 
Let me discuss the interesting control flow of the program. Line 4 creates eight default-constructed threads, which the coroutine performJob (line 5) takes by reference. Further, the reference becomes the argument for creating MyAwaitable{out} (line 1). Depending on the value of res (line 6), and, therefore, the return value of the function await_ready, the Awaitable continues (res is true) to run or is suspended (res is false). In case MyAwaitable is suspended, the function await_suspend (line 7) is executed. Thanks to the assignment of outerThread (line 8), it becomes a running thread. The running threads must outlive the lifetime of the coroutine. For this reason, the threads have the scope of the main function.
 

What's next?

DONE: I have written almost 100 posts about C++20. In my next post I want to say a few concluding words about C++20 and answer the questions "What's next" regarding C++.

 

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, Louis St-Amour, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Tobi Heideman, Daniel Hufschläger, Red Trip, Alexander Schwarz, Tornike Porchxidze, Alessandro Pezzato, Evangelos Denaxas, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Dimitrov Tsvetomir, Leo Goodstadt, Eduardo Velasquez, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, and Michael Young.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, and Rusty Fleming.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

New

Contact Me

Modernes C++,

RainerGrimmSmall

 

Comments   

0 #1 Gunnar Skogsholm 2021-04-20 17:34
If you're going to run it on a different thread anyways, what's the point of coroutines?
Quote
0 #2 Rainer Grimm 2021-04-22 18:41
Quoting Gunnar Skogsholm:
If you're going to run it on a different thread anyways, what's the point of coroutines?

The thread is started automatically if the job is not done.
Quote

My Newest E-Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Interactive Course: The All-in-One Guide to C++20

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 5980

Yesterday 7646

Week 39275

Month 105941

All 7373781

Currently are 159 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments