Page 50 - MSDN Magazine, June 2018
P. 50
Figure 5 Implementing IAsyncAction
The beauty of coroutines is that there’s a single abstraction both for producing async objects and for consuming those same async objects. An API or component author might implement an async method as described earlier, but an API consumer or app developer could also use coroutines to call and wait for its com- pletion. Let’s now rewrite the main function from Figure 6 to use a coroutine to do the waiting:
IAsyncAction MainAsync() {
hstring result = co_await ReadAsync();
printf("%ls\n", result.c_str()); }
int main() {
MainAsync().get(); }
I have essentially taken the body of the old main function and moved it into the MainAsync coroutine. The main function uses the get method to prevent the app from terminating while the coroutine completes asynchronously. The MainAsync function has something new and that’s the co_await statement. Rather than using the get method to block the calling thread until ReadAsync completes, the co_await statement is used to wait for the ReadAsync function to complete in a cooperative or non-blocking manner. This is what I meant by a suspension point. The co_await statement represents a suspension point. This app only calls ReadAsync once, but you can imagine it being called multiple times in a more interesting app. The first time it gets called, the MainAsync coroutine will ac- tually suspend and return control to its caller. The second time it’s called, it won’t suspend at all but rather return the value directly.
Coroutines are very new to many C++ developers, so don’t feel bad if this still seems rather magical. These concepts will become quite clear as you start writing and debugging coroutines yourself. The good news is that you already know enough to begin to make effective use of coroutines to consume async APIs provided by Windows. For example, you should be able to reason about how the console app in Figure 7 works.
Figure 7 An Example Console App with Coroutines
struct MyAsync : implements<MyAsync, IAsyncAction, IAsyncInfo> {
// IAsyncInfo members ... uint32_t Id() const; AsyncStatus Status() const; HRESULT ErrorCode() const; void Cancel() const;
void Close() const;
// IAsyncAction members ...
void Completed(AsyncActionCompletedHandler const& handler) const; AsyncActionCompletedHandler Completed() const;
void GetResults() const;
};
Figure 6 A Function That Downloads and Returns a Cached Value
hstring m_cache;
IAsyncOperation<hstring> ReadAsync() {
if (m_cache.empty()) {
// Download and cache value ... }
co_return m_cache; }
int main() {
hstring message = ReadAsync().get();
printf("%ls\n", message.c_str()); }
A coroutine (of this sort) must have a co_return statement or a co_await statement. It may, of course, have multiple such state- ments, but must have at least one of these in order to actually be a coroutine. As you might expect, a co_return statement doesn’t introduce any kind of suspension or asynchrony. Therefore, this CopyAsync function produces an IAsyncAction that completes immediately or synchronously. I can illustrate this as follows:
IAsyncAction Async() {
co_return; }
int main() {
IAsyncAction async = Async();
assert(async.Status() == AsyncStatus::Completed); }
The assertion is guaranteed to be true. There’s no race here. Because CopyAsync is just a function, the caller is blocked until it returns and the first opportunity for it to return happens to be the co_return statement. What this means is that if you have some async contract that you need to implement, but the implementation doesn’t actually need to introduce any asynchrony, it can simply return the value directly, and without blocking or introducing a context switch. Consider a function that downloads and then returns a cached value, as shown in Figure 6.
The first time ReadAsync is called, the cache is likely empty and the result is downloaded. Presumably this will suspend the coroutine itself while this takes place. Suspension implies that execution returns to the caller. The caller is handed an async object that has not, in fact, completed, hence the need to some- how wait for completion.
#include "winrt/Windows.Web.Syndication.h"
using namespace winrt;
using namespace Windows::Foundation; using namespace Windows::Web::Syndication;
IAsyncAction MainAsync() {
Uri uri(L"https://kennykerr.ca/feed");
SyndicationClient client;
SyndicationFeed feed = co_await client.RetrieveFeedAsync(uri);
for (auto&& item : feed.Items()) {
hstring title = item.Title().Text();
printf("%ls\n", title.c_str()); }
}
int main() {
init_apartment();
MainAsync().get(); }
44 msdn magazine
C++