Page 46 - MSDN Magazine, June 2018
P. 46
All four of the async interfaces logically derive from the IAsyncInfo interface. There’s very little you can do with IAsyncInfo and it’s regrettable that it even exists because it adds a bit of overhead. The only IAsyncInfo members you should really consider are Status, which can tell you whether the async method has completed, and Cancel, which can be used to request cancellation of a long-running operation whose result is no longer needed. I nitpick this design because I really like the async pattern in general and just wish it were perfect because it’s so very close.
The Status member can be useful if you need to determine whether an async method has completed without actually waiting for it. Here’s an example:
auto async = ReadAsync();
if (async.Status() == AsyncStatus::Completed) {
auto result = async.GetResults(); printf("%ls\n", result.c_str());
Each of the four async interfaces, not IAsyncInfo itself, provides individual versions of the GetResults method that should be called only after you’ve determined that the async method has completed.
Figure 3 Notifying a Waiting Thread Using an Event
Don’t confuse this with the get method provided by C++/WinRT. While GetResults is implemented by the async method itself, get is implemented by C++/WinRT. GetResults won’t block if the async method is still running and will likely throw an hresult_illegal_ method_call exception if called prematurely. You can, no doubt, begin to imagine how the blocking get method is implemented. Conceptually, it looks something like this:
auto get() const {
if (Status() != AsyncStatus::Completed) {
// Wait for completion somehow ... }
return GetResults(); }
The actual implementation is a bit more complicated, but this captures the gist of it. The point here is that GetResults is called regardless of whether it’s an IAsyncOperation, which returns a value, or IAsyncAction, which doesn’t. The reason for this is that GetResults is responsible for propagating any error that may have occurred within the implementation of the async method and will rethrow an exception as needed.
The question that remains is how the caller can wait for com- pletion. I’m going to write a non-member get function to show you what’s involved. I’ll start with this basic outline, inspired by the previous conceptual get method:
template <typename T> auto get(T const& async) {
if (async.Status() != AsyncStatus::Completed) {
// Wait for completion somehow ... }
return async.GetResults(); }
I want this function template to work with all four of the async interfaces, so I’ll use the return statement unilaterally. Special pro- vision is made in the C++ language for genericity and you can be thankful for that.
Each of the four async interfaces provides a unique Completed member that can be used to register a callback—called a delegate— that will be called when the async method completes. In most cases, C++/WinRT will automatically create the delegate for you. All you need to do is provide some function-like handler, and a lambda is usually the simplest:
async.Completed([](auto&& async, AsyncStatus status) {
// It's done! });
The type of the delegate’s first parameter will be that of the async interface that just completed, but keep in mind that completion should be regarded as a simple signal. In other words, don’t stuff a bunch of code inside the Completed handler. Essentially, you should regard it as a noexcept handler because the async method won’t itself know what to do with any failure occurring inside this handler. So what can you do?
Well, you could simply notify a waiting thread using an event. Figure 3 shows what the get function might look like.
C++/WinRT’s get methods use a condition variable with a slim reader/writer lock because it’s slightly more efficient. Such a variant might look something like what’s shown in Figure 4.
}
template <typename T> auto get(T const& async) {
if (async.Status() != AsyncStatus::Completed) {
handle signal = CreateEvent(nullptr, true, false, nullptr);
async.Completed([&](auto&&, auto&&) {
SetEvent(signal.get()); });
WaitForSingleObject(signal.get(), INFINITE); }
return async.GetResults(); }
Figure 4 Using a Condition Variable with a Slim Reader/Writer Lock
template <typename T> auto get(T const& async) {
if (async.Status() != AsyncStatus::Completed) {
slim_mutex m; slim_condition_variable cv; bool completed = false;
async.Completed([&](auto&&, auto&&) {
{
slim_lock_guard const guard(m); completed = true;
}
cv.notify_one(); });
slim_lock_guard guard(m);
cv.wait(m, [&] { return completed; }); }
return async.GetResults(); }
40 msdn magazine
C++