Quantcast
Channel: The Old New Thing
Viewing all articles
Browse latest Browse all 3085

Turning anything into a fire-and-forget coroutine

$
0
0

Last time, we wrote a helper function for converting an awaitable into a winrt::fire_and_forget, as well as another helper function that takes a lambda that returns an awaitable, and which invokes the lambmda as a winrt::fire_and_forget.

After I wrote the two functions, I wondered if I could unify them. Mostly because I wanted to use the same name no_await for both functions.

This took me down the horrible rabbit hole known as C++ template metaprogramming. I wanted two versions of the function, one that is used if the parameter is awaitable, and another that is used if the parameter is a functor. This led me to try using things like std::enable_if to detect which case I’m in, and that led to lots of frustration, especially because there’s no easy way to detect if a type is awaitable. My closest approach was

template<typename T, typename Promise = std::void_t<>>
struct is_awaitable : std::false_type {};

template<typename T>
struct is_awaitable<T, std::void_t<typename std::experimental::coroutine_traits<T>::promise_type>> : std::true_type {};

template<typename T>
inline constexpr bool is_awaitable_v = is_awaitable<T>::value;

which infers that a type is awaitable by sniffing whether it has an associated promise_type. This isn’t foolproof, because some types like winrt::fire_and_forget have a promise_type that cannot be awaited.

My first realization was that I could flip the test. Instead of checking whether the argument is awaitable, I check whether it is invokable.

My second realization was that I didn’t have to do fancy template metaprogramming at all. I could take advantage of the new if constexpr feature.

template<typename T>
fire_and_forget no_await(T t)
{
    if constexpr (std::is_invocable_v<T>)
    {
        co_await t();
    }
    else
    {
        co_await t;
    }
}

Now you can use no_await with awaitables or functors that return awaitables.

void Stuff()
{
  // Start this operation but don't wait for it to finish
  no_await(DoSomethingAsync());

  // Start this sequence of things and don't wait for
  // them to finish.
  no_await([=]() -> IAsyncAction
  {
    co_await Step1Async();
    // Step 2 doesn't start until Step 1 completes.
    co_await Step2Async();
  });
}

On the other hand, for the case of the lambda passed to no_await, you could just declare your lambda as returning a winrt::fire_and_forget, and then you wouldn't need no_await.

void Stuff()
{
  // Start this operation but don't wait for it to finish
  no_await(DoSomethingAsync());

  // Start this sequence of things and don't wait for
  // them to finish.
  invoke_async_lambda([=]() -> winrt::fire_and_forget
  {
    co_await Step1Async();
    // Step 2 doesn't start until Step 1 completes.
    co_await Step2Async();
  });
}

But I like the fact that the first example uniformly uses the name no_await to describe the concept of "I'm not going to wait for this thing to finish." And also I'm perhaps unduly attached to the cute name.

The post Turning anything into a fire-and-forget coroutine appeared first on The Old New Thing.


Viewing all articles
Browse latest Browse all 3085

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>