December 11th, 2019

C++ coroutines: Framework interop

So far, we’ve been looking at the basics of awaitable objects. Even though we barely know anything beyond await_suspend, we already know enough to allow us to start diving deeper.

It is frequently the case that you need your awaiter to interact with something outside the C++ standard library. To make it easier to integrate coroutines with existing frameworks, the coroutine_handle can be converted to a void* by calling its address() method, and the resulting void* can be converted back to an equivalent coroutine_handle by calling from_address().¹

Most frameworks let you pass a pointer-sized piece of data around to help remember state, and being able to convert a handle into a pointer (and back) lets you pass the coroutine handle through such state parameters. Otherwise, you’d have to copy the coroutine_handle to the heap and pass the address of the heap block, and then keep track of when to free the heap block.

Let’s demonstrate this by reimplementing resume_new_thread in terms of Win32 functions instead of the std::thread standard library class.

struct resume_new_thread : std::experimental::suspend_always
{
  void await_suspend(
      std::experimental::coroutine_handle<> handle)
  {
    HANDLE thread = CreateThread(nullptr, 0, callback,
                        handle.address(), 0, &threadId);
    if (!thread) throw some_kind_of_error();
    CloseHandle(thread);
  }

  DWORD CALLBACK callback(void* parameter)
  {
    auto handle = std::experimental::coroutine_handle<>::
                    from_address(parameter);
    handle();
    return 0;
  }
};

The basic idea is the same as last time: When the coroutine suspends, schedule the continuation on a newly-created thread.

The Create­Thread function allows you to pass a single pointer-sized piece of data, so we convert our handle to a void* by calling the address method, and pass that pointer as the reference data to the thread procedure. The thread procedure converts the pointer back into a coroutine handle by calling from_address, and then invokes the coroutine to resume execution.

If terseness is your game, you could inline the thread procedure as a stateless lambda, taking advantage of the implicit conversion from a stateless lambda to a function pointer.

struct resume_new_thread : std::experimental::suspend_always
{
  void await_suspend(
      std::experimental::coroutine_handle<> handle)
  {
    HANDLE thread = CreateThread(nullptr, 0,
      [](void* parameter) -> DWORD
      {
        std::experimental::coroutine_handle<>::
                    from_address(parameter)();
        return 0;
      }, handle.address(), 0, &threadId);
    if (!thread) throw some_kind_of_error();
    CloseHandle(thread);
  }
};

Next time, we’ll use what we’ve learned about awaiters to develop a way to override C++/WinRT coroutine threading defaults.

¹ The method names address and from_address give a strong clue as to what the void* represents: it’s the address of runtime-managed coroutine state, known in the language specification as a coroutine frame.

Topics
Code

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.

1 comment

Discussion is closed. Login to edit/delete existing comments.

  • GL

    I believe you need DWORD threadId; before CreateThread and static modifier for the callback.