Page 51 - MSDN Magazine, June 2018
P. 51

Give it a try right now and see just how much fun it is to use modern C++ on Windows.
Coroutines and the Thread Pool
Creating a basic coroutine is trivial. You can very easily co_await some other async action or operation, simply co_return a value, or craft some combination of the two. Here’s a coroutine that’s not asynchronous at all:
IAsyncOperation<int> return_123() {
co_return 123; }
Even though it executes synchronously, it still produces a com- pletely valid implementation of the IAsyncOperation interface:
int main() {
int result = return_123().get();
assert(result == 123); }
Here’s one that will wait for five seconds before returning the value:
using namespace std::chrono;
IAsyncOperation<int> return_123_after_5s() {
co_await 5s;
co_return 123; }
The next one is ostensibly going to execute asynchronously and yet the main function remains largely unchanged, thanks to the get function’s blocking behavior:
int main() {
int result = return_123_after_5s().get();
assert(result == 123); }
The co_return statement in the last coroutine will execute on the Windows thread pool, because the co_await expression is a chrono duration that uses a thread pool timer. The co_await statement rep- resents a suspension point and it should be apparent that a coroutine may resume on a completely different thread following suspension. You can also make this explicit using resume_background:
IAsyncOperation<int> background_123() {
co_await resume_background();
co_return 123; }
There’s no apparent delay this time, but the coroutine is guar- anteed to resume on the thread pool. What if you’re not sure? You might have a cached value and only want to introduce a context switch if the value must be retrieved from latent storage. This is where it’s good to remember that a coroutine is also a function, so all the normal rules apply:
IAsyncOperation<int> background_123() {
static std::atomic<int> result{0};
if (result == 0) {
co_await resume_background(); result = 123;
co_return result; }
This is only conditionally going to introduce concurrency. Multiple threads could conceivably race in and call background_123,
Figure 8 Reading a Value from Storage After a Signal Is Raised
handle m_signal{ CreateEvent(nullptr, true, false, nullptr) }; std::atomic<int> m_value{ 0 };
IAsyncAction prepare_result() {
co_await 5s;
m_value = 123; SetEvent(m_signal.get());
}
IAsyncOperation<int> return_on_signal() {
co_await resume_on_signal(m_signal.get());
co_return m_value; }
}
causing a few of them to resume on the thread pool, but eventually the atomic variable will be primed and the coroutine will begin to complete synchronously. That is, of course, the worst case.
Let’s imagine the value may only be read from storage once a signal is raised, indicating that the value is ready. We can use the two coroutines in Figure 8 to pull this off.
The first coroutine artificially waits for five seconds, sets the value, and then signals the Win32 event. The second coroutine waits for the event to become signaled, and then simply returns the value. Once again, the thread pool is used to wait for the event, leading to an efficient and scalable implementation. Coordinating the two coroutines is straightforward:
int main() {
prepare_result();
int result = return_on_signal().get();
assert(result == 123); }
The main function kicks off the first coroutine but doesn’t block waiting for its completion. The second coroutine immediately begins waiting for the value, blocking as it does so.
Wrapping Up
Well, I’ve gone pretty deep on async and coroutines in C++. In this article I’ve focused mostly on the thread pool, or what might be called background threads, but you can dive deeper by visiting the online version of this article at aka.ms/M1h2v0. The Web article includes an additional section that explores ways to take precise control over execution context. Coroutines can be used to introduce concurrency or deal with latency in other APIs, so it helps to address confusion that can arise around the execution context of a given coroutine at any particular point in time. Check out the online-exclusive content in the article at aka.ms/M1h2v0 to learn more. There’s always more to say about concurrency. It’s such a fascinating topic, and I hope this introduc- tion will get you excited about how simple it is to deal with async in your C++ apps and components and the sheer power at your fingertips when you begin to use C++/WinRT. n
Kenny Kerr is an author, systems programmer and the creator of C++/WinRT. He’s also an engineer on the Windows team at Microsoft where he’s designing the future of C++ for Windows, enabling developers to write beautiful highperformance apps and components with incredible ease.
msdnmagazine.com
June 2018 45


































































































   49   50   51   52   53