C++/WinRT provides an implementation for Windows Runtime asynchronous actions and operations, and they even support cancellation, even if your code doesn’t realize it.
Whenever¹ your coroutine performs a co_await
, the C++/WinRT library checks whether the coroutine has already been cancelled.² If so, then it abandons the coroutine and goes to the Canceled
state.
IAsyncAction ProcessAllWidgetsAsync() { auto widgets = co_await GetAllWidgetsAsync(); for (auto&& widget : widgets) { ProcessWidget(widget); } co_await ReportStatusAsync(WidgetsProcessed); }
This function gathers all the widgets and then processes them one by one. But say there are thousands of widgets, and you try to Cancel
the operation:
IAsyncAction DoOperationAsync() { // Remember the operation so we can cancel it. operation = ProcessAllWidgetsAsync(); co_await operation; } void CancelOperation() { operation.Cancel(); }
When the Cancel
is called, the C++/WinRT library remembers that the coroutine has been cancelled and looks for a chance to stop the coroutine. But right now, the coroutine is busy running the loop inside ProcessAllWidgets
, and the C++/WinRT library doesn’t get control until the co_await
when it comes time to report the status. Once that happens, the coroutine stops executing and reports its cancellation.³
That could be hours from now.
You can hasten the cancellation process in your coroutine by polling for cancellation.
IAsyncAction ProcessAllWidgetsAsync() { auto cancellation = co_await get_cancellation_token(); auto widgets = co_await GetAllWidgetsAsync(); for (auto&& widget : widgets) { if (cancellation()) co_return; ProcessWidget(widget); } co_await ReportStatusAsync(WidgetsProcessed); }
The co_await get_
produces a cancellation token for the current coroutine.⁴
Before processing each widget, we check if we have been cancelled. If so, then we just give up immediately. The co_return
is another point where the C++/WinRT library regains control, and that also processes the pending cancellation.
But wait, what if the caller tries to cancel the operation while the GetAllWidgetsAsync
is in progress? Control is now inside that other asynchronous operation, and it could take a very long time to get all of the widgets. Next time, we’ll look at how to propagate the cancellation into dependent coroutines.
¹ There are a few exceptions to this rule, but it’s true enough.
² I spell cancelled with two L’s.
³ This highlights the importance of using RAII types for all of your cleanup. If the coroutine stops executing due to cancellation, then its automatic objects are destructed according to the usual rules of C++, and that’s where your abnormal cleanup happens. We’ll talk more about this soon.
⁴ The co_await get_
is one of the exceptions to the rule that co_await
always checks for cancellation. In this case, co_await get_
doesn’t actually “await” anything. Rather, it’s a backdoor into the C++/WinRT library. We’ll learn more about how these backdoors work when we look at how to implement your own coroutines in C++20, at some unspecified point in the future.
I've always been curious about patterns in usages of the one-l canceled and two-l cancelled, as well as for other inflected forms (ending in -ing, -able, -er, and -ation) and for other verbs ending in -el (label, travel, level, model, etc.).
Some dictionaries describe the one-l forms as American and the two-l forms as British, but that hasn't been entirely consistent with what I've observed in practice. I'm not even consistent myself, I've used both...
> Thankfully there’s no three-l cancellled. (Apologies to Ogden Nash.)
Very droll.
The general reason is that the word "canceled" with one L (along with traveled, leveled, etc.) was promoted by Webster's Dictionary -- an American publication -- in the 19th century. The "two L" variant is the original and is still the spelling used by people outside the United States.
This desire to shorten certain words was pretty arbitrary. "Spelled", for instance, still has two Ls.
Funny how history goes..... some guy named Noah had...
Hooray, something other than arcane C++ metaprogramming nonsense. I can actually understand what’s going on now! (Sorry. I’m just not a C++ person…)