We can not co_await
from non-coroutine, so, we can not do something like that:
void main(void)
{
co_await my_coroutine();
}
Let suppose that my_coroutine
is something that we don't control directly and can not create its callback-based version. So how to call it at all?
Naive idea is something like that:
void main(void)
{
my_coroutine();
}
It will not work of course, because we'll exit main
before my_coroutine
will finish.
So what to do? One working idea will look like that:
void main(void)
{
auto my_promise = my_coroutine();
bool my_promise_finished = false;
while(!my_promise_finished)
{
/* somehow detect indirectly/in undocumented way that my_coroutine finished */
my_promise_finished = is_my_promised_finished();
Sleep(1000);
}
}
The idea is ugly. 1. We have to use undocumented/unreliable way to detect whether coroutine finished. 2. We block entire main thread to wait while another thread will do something.
Are there any good/official way how to call the very first/top coroutine in C++ program and wait for its completion without blocking entire thread for that job.
The example is practical enough. For example, we may have URL download utility that calls single top-level coroutine download_1000_urls
and it may create multiple threads on its own and download a lot of data from Internet; it is still single top-level coroutine that main
has to call somehow, similarly to old-style asynchronous Application.Run
method.
While the C++20 coroutine feature does implement "coroutines", these are not like "coroutines" that you may see in other languages. I mean, they are, but not in the way you're thinking.
For this question, the most important thing is this: a coroutine is an implementation detail of how a function works.
Your my_coroutine
is not actually a coroutine from the perspective of any code that is not inside of my_coroutine
. It's just a function. Like any other function, it takes some number of parameters, performs some operation, and provides some kind of return value to the caller.
As such, if a function is a coroutine, and that coroutine wants to pass some delayed value or control mechanism to its caller, the only interface it has is the return value of the function. That is how external code interacts with the coroutine. If the return value so allows it, it can provide a mechanism to check to see if it is completed and to continue the coroutine if it isn't. It can provide a mechanism to request the next value for generator-style coroutines. It can do a lot of things.
But in all of these cases, it is up to the coroutine itself to provide those interfaces. There is no one-size-fits-all answer for how you "control a coroutine". In a real example, my_coroutine
will return some object and members of that object are your control mechanism. Whatever interfaces it provides are how you control it.
So if my_coroutine
is some multi-step process, and my_coroutine
expects the caller of it to tell it to execute the next step, then my_coroutine
needs to return an object that facilitates this. Alternatively, if my_coroutine
registers itself with some asynchronous facility which will automatically resume it once that process it is waiting on has completed, then my_coroutine
's return value won't have a way to execute the next step because that's someone else's job.
The interface a particular function provides tells you how to interact with it. There is no uniform interface because there are dozens of ways to use a function that can be paused and resumed. You build the interface that your particular use case needs.