May 26th, 2026
likeintriguingmind blown6 reactions

If C# and JavaScript lets me await a Windows Runtime asynchronous operation more than once, why not C++/WinRT?

The Windows Runtime expresses asynchronous execution in the form of types like IAsync­Operation. Different languages choose to represent this concept in different ways.

  • C# wraps it inside a System.Threading.Tasks.Task.
  • JavaScript wraps it inside a Promise.
  • Python wraps it inside an asyncio.Future.

All of the above wrappers support multiple continuations, which means that you can call await on them multiple times.

But C++/WinRT doesn’t provide such a wrapper. C++/WinRT just exposes the original IAsync­Operation. The IAsync­Operation doesn’t allow multiple continuations to be attached, so you cannot co_await it multiple times.

Boo.

Why doesn’t C++/WinRT get with the program and provide a wrapper that supports multiple continuations?

Well, one reason is that C#’s System.Threading.Tasks.Task, JavaScript’s Promise, and Python’s asyncio.Future are part of the respective languages’ standard libraries. This makes them an obvious choice for projection since they are a way to represent asynchronous execution that is universally understood by anybody writing code in that language. Furthermore, the fact that they are standard library features lets the projections build on the work of others: The standard library maintainers have already implemented, tested, and optimized these classes.

C++ does not have a standard library for asynchronous execution. Instead, C++ provides raw materials out of which you can write your own library. And we wrote one such library back when we learned about C++ coroutines and implemented a simple_task class.

The lack of a standard library task type means that there isn’t a pre-existing wrapper that C++ projections could take advantage of. It’s every man for himself.

C++/WinRT provides a minimal coroutine promise for IAsync­Operation in deference to the general C++ principle of “you don’t pay for what you don’t use.” The overwhelming majority of the time, you have no need to await an IAsync­Operation more than once, so why make every IAsync­Operation pay for it?

Next time, we’ll look at a case where you would want to await an IAsync­Operation more than once, and see what we can do to work around the C++/WinRT limitation.

Bonus chatter: There does exist a pre-existing library that supports multiple continuations: The Parallel Patterns Library concurrency::task object. The older C++/CX projection does not natively wrap the IAsyncOperation^, but the Parallel Patterns Library does have special knowledge of the C++/CX IAsyncOperation^, so you can wrap an IAsyncOperation^ inside a PPL task and co_await the task more than once.

So I guess you could say that somebody already wrote the wrapper for you. Unfortunately, that wrapper is quite bulky because the PPL library has so many features, most of which you probably aren’t using. You could say that while C++/WinRT tries to be minimalist, PPL tries to be maximalist. It’s easier to start small and grow and it is to start big and try to pare back. (And C++/WinRT generally delegates the “grow” part to the Windows Implementation Library, which adds additional features with headers like cppwinrt_helpers.h and cppwinrt_authoring.h.)

Topics

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

4 comments

Sort by :
  • Michael Croes 1 week ago

    Yes C# has Task, but it’s allocated on the heap. If you want something more performant you’ll end up with implementations that are often not suitable for multiple awaits/continuations either. Personally I wouldn’t have minded if C# were a bit more performance oriented from the start, but luckily they’re at least trying to set that straight now. I can only guess with WinRT performance was not an afterthought 10 years later…

  • Hamed 1 week ago

    What C# calls a ‘Task’, JS calls a ‘Promise’, and Python calls a ‘Future’. Software engineers are absolute geniuses at naming things 😂

    • Hải Đinh 1 week ago

      And c++ “has” all of them lol
      en.cppreference.com/cpp/thread/future
      en.cppreference.com/cpp/thread/packaged_task
      en.cppreference.com/cpp/thread/promise

  • LB 1 week ago

    It’s funny to see the PPL described as bulky when I found it to be missing many features I needed and I ended up having to wrap it myself. Working around the terminate-on-unobserved-exception behavior was also quite annoying, but I wouldn’t have known about it without you mentioning it in a prior blog post, so thanks for that.