C++1z coroutine threading context and coroutine scheduling
The opposite!
C++ coroutine is all about control. the key point here is the
void await_suspend(std::experimental::coroutine_handle<> handle)
function.
evey co_await
expects awaitable type. in a nutshell, awaitable type is a type which provide these three functions:
bool await_ready()
- should the program halt the execution of the coroutine?void await_suspend(handle)
- the program passes you a continuation context for that coroutine frame. if you activate the handle (for example, by callingoperator ()
that the handle provides - the current thread resumes the coroutine immediately).T await_resume()
- tells the thread which resumes the coroutine what to do when resuming the coroutine and what to return fromco_await
.
so when you call co_await
on awaitable type, the program asks the awaitable if the coroutine should be suspended (if await_ready
returns false) and if so - you get a coroutine handle in which you can do whatever you like.
for example, you can pass the coroutine handle to a thread-pool. in this case a thread-pool thread will resume the coroutine.
you can pass the coroutine handle to a simple std::thread
- your own create thread will resume the coroutine.
you can attach the coroutine handle into a derived class of OVERLAPPED
and resume the coroutine when the asynchronous IO finishes.
as you can see - you can control where and when the coroutine is suspended and resumes - by managing the coroutine handle passed in await_suspend
. there is no "default scheduler" - how you implement you awaitable type will decide how the coroutine is schedueled.
So, what happens in VC++? unfortunately, std::future
still doesn't have then
function, so you can't pass the coroutine handle to a std::future
. if you await on std::future
- the program will just open a new thread. look at the source code given by the future
header:
template<class _Ty> void await_suspend(future<_Ty>& _Fut, experimental::coroutine_handle<> _ResumeCb) { // change to .then when future gets .then thread _WaitingThread([&_Fut, _ResumeCb]{ _Fut.wait(); _ResumeCb(); }); _WaitingThread.detach(); }
So why did you see a win32 threadpool-thread if the coroutines are launched in a regular std::thread
? that's because it wasn't the coroutine. std::async
calls behind the scenes to concurrency::create_task
. a concurrency::task
is launched under the win32 threadpool by default. after all, the whole purpose of std::async
is to launch the callable in another thread.