The following code doesn't work as my expectation.
#include <iostream>
#include <coroutine>
#include <vector>
struct symmetic_awaitable
{
std::coroutine_handle<> _next_h;
symmetic_awaitable(std::coroutine_handle<> h) : _next_h(h) {}
constexpr bool await_ready() const noexcept { return false; }
std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
{
return _next_h;
}
constexpr void await_resume() const noexcept {}
};
struct return_object : std::coroutine_handle<>
{
struct promise_type
{
return_object get_return_object()
{
return std::coroutine_handle<promise_type>::from_promise(*this);
}
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
return_object(std::coroutine_handle<promise_type> h) : std::coroutine_handle<>(h) {}
};
std::vector<return_object> co_list;
return_object fa()
{
std::cout << "fa1" << std::endl;
co_await symmetic_awaitable(co_list[1]);
std::cout << "fa2" << std::endl;
co_return;
}
return_object fb()
{
std::cout << "fb1" << std::endl;
co_await symmetic_awaitable(co_list[2]);
std::cout << "fb2" << std::endl;
co_await std::suspend_always{};
std::cout << "fb3" << std::endl;
co_return;
}
return_object fc()
{
std::cout << "fc1" << std::endl;
co_await symmetic_awaitable(co_list[1]);
std::cout << "fc2" << std::endl;
co_return;
}
int main()
{
auto a = fa();
auto b = fb();
auto c = fc();
co_list.push_back(a);
co_list.push_back(b);
co_list.push_back(c);
a.resume();
std::cout << "end" << std::endl;
a.destroy();
b.destroy();
c.destroy();
}
I think the output will be
fa1
fb1
fc1
fb2
fa2
end
But the actual output is
fa1
fb1
fc1
fb2
end
Then I replace all co_await symmetic_awaitable(co_list[i])
with co_list[i].resume
. The ouput is very strange
fa1
fb1
fc1
fb1
fc1
..... // the following is infinite loop of "fb1 fc1"
.....
.....
The C 20 coroutine shadow too many details, such that the code can't work normally as my expectation unless I know them all clearly.
Here are my questions after I read cppreference:
1. What's the defference of "caller" and "resumer" ?
a call b.resume()
, then a is the resumer or caller of b ?
2. What's the exact meanning of "suspended" ?
a call b.resume()
, then a is suspended or running? a resume b through co_await
, then a is suspended or running ?
CodePudding user response:
If a function is a coroutine, it can only become suspended in one of the following ways:
- When the coroutine starts, if the promise initially suspends.
- When a
co_await
expression (or equivalent, likeco_yield
) is directly invoked. - When the coroutine
co_return
s, if the promise finally suspends.
Nothing else can cause a coroutine to become suspended. The "co" in "coroutine" represents cooperative multitasking. That means there's no multitasking unless the function(s) involved cooperate. Explicitly.
Your assumptions about how you expect your code to work seem to suggest that you believe that coroutines have some kind of execution stack. That when await_suspend
is called, the current coroutine gets put onto a stack that will be poped when the coroutine handle you returned finishes in some way. And therefore, when you invoke co_await std::suspend_always{};
this will resume the previously suspended coroutine.
None of that is true. Not unless you build that machinery yourself.
The coroutine system does exactly and only what you tell it to do.
The call stack immediately after calling a.resume()
looks like this:
main()
fa()
When fa
suspends and resumes fb
, it now looks like this:
main()
fb()
fa
is gone. You suspended it. It is no longer on the call stack. And it will only ever be resumed if you explicitly ask to resume it.
If you want fa
's suspension into fb
to mean that fa
will be continued after fb
finishes, then you must build that into your coroutine machinery. It doesn't just happen; it's your responsibility to make it happen.
Your await_suspend
code needs to take the handle it is given (which refers to fa
) and store it in a place where, when fb
is finished, it can resume fa
. This will typically be in fb
's promise object, so that final_suspend
can resume it (typically passing along the data generated by fb
). Remember: the final-suspend point will co_await
on whatever the promise's final_suspend
returns, so you can just return the handle of the coroutine you want to resume.
What's the defference of "caller" and "resumer" ?
I don't know what that means. I suspect you are asking what the difference is between co_await
ing on something and directly calling coroutine_handle::resume
function.
As previously indicated, outside of the initial and final suspend points, only co_await
(or equivalent) expressions can cause a coroutine to suspend. Calling resume
on a handle is like you called into the middle of a function. It works just like any other function call; it goes onto the stack and so forth.
Having co_await
resume a coroutine is different. When await_suspend
returns a coroutine handle, this replaces your coroutine with the resumed coroutine on the call stack. That's the whole point of suspending the coroutine.