On 6/30/26 16:40, Steve Gerbino via Boost wrote:
On Tuesday, June 30th, 2026 at 2:45 PM, Rainer Deyke via Boost <boost@lists.boost.org> wrote:
The strength of coroutines is that they allow chains of callbacks to be written as if they were linear functions. Using capy::run reads like a callback. Using resume_on reads like a linear function.
Callback version:
task<> f() { auto image = co_await load_image(); main_thread_callbacks.push_back([image = std::move(image)] { upload_texture_to_opengl(image); }); }
capy::run version:
task<> f() { auto image = co_await load_image(); capy::run(main_thread_executor)([image = std::move(image)] { upload_texture_to_opengl(image); }); }
resume_on version:
task<> f() { auto image = co_await load_image(); resume_on(main_thread_executor); upload_texture_to_opengl(image); }
Can you see the similarity of the first two, and how the third one differs?
I found your argument compelling which led to me to think more about resume_on(). I also injected my own assumption that you meant "co_await resume_on(ex);" -- correct me if I'm wrong.
Yes, that is what I meant.
Let's explore another example:
task<> task_B() { co_await resume_on( other_ex ); }
task<> task_A() { for(;;) co_await task_B(); }
I think this may illustrate the point of contention. If you launch task_A on a certain executor, we currently can make that guarantee to the user that task_A will run on said executor.
I suspect the implementation you desire for resume_on(ex) is essentially a cheap/efficient executor switch. If we did that, task_B can switch the executor that task_A resumes on which breaks what we're trying to do here. If that implementation is not correct, then it needs to move closer to what run() already does.
Yes, the task would need to store two executors, the current executor and the original executor, so that it can propagate the current executor on co_await but return to the original executor on co_return. And this does come at a cost. At the very least, an extra pointer to store the original executor, and possibly an extra check to see if we need to switch executors. (I'm thinking the extra check may already be there, because a `task` is an IoAwaitable and using `dispatch` to resume the calling coroutine is part of the IoAwaitable protocol and `dispatch` contains the check. But it's also possible that `task` is optimized to skip the check.) If the benefit of adding `resume_on` is not worth the cost, well, fair enough. It would be nice to limit the costs to just those tasks that use `resume_on`, but this is not easy. It would be nice to have a type like this that tasks can use to opt into resume_on functionality: class executor_switcher { public: auto switch_to(executor_ref executor) { /* ... */ } executor_switcher() { this->original_executor = co_await this_coro::executor; } executor_switcher(executor_ref executor) { this->original_executor = co_await this_coro::executor; co_await this->switch_to(executor); } ~executor_switcher() { co_await this->switch_to(this->original_executor); } private: executor_ref original_executor; }; Unfortunately this is impossible, since neither the constructor nor the destructor of executor_switcher is allowed to use co_await. -- Rainer Deyke - rainerd@eldwood.com