cppcoro
cppcoro copied to clipboard
Fire and forget?
From a non-coroutine context, there is sync_wait
to do the equivalent of a blocking co_await
.
But is there some way to fire and forget a coroutine? (ie. start its execution either directly on the current thread -- where it returns at the first suspend point -- or on a designated executor -- where it returns immediately.)
Boost.Asio's co_spawn
lets you request a detached
spawn for this case, for example.
(Bringing back the eager tasks might be another alternative. I was a little surprised to discover that task
is lazy, since I'm most familiar with .NET eager Tasks. This is likely to surprise others as well, and it does seem like a possible loss of performance if a task scheduled for a different thread is not immediately awaited in the current thread, unless I'm missing something.)
Would be good to have something like std::async for task as mentioned in P1056R0
Coming from javascript, I'm also surprised that tasks are lazy. The following workflow is great with coroutines, but I don't think it's possible to achieve with only lazy tasks:
task<std::string> get_expensive_child_process() {
// Start child process
// co_return child process' stdout
}
task<> mainProgram() {
// The process should be (eagerly!) started here
task<std::string> t = get_expensive_child_process();
// ...
// continue with the program as normal, probably taking quite some time
// ...
// now we're ready to receive the child's data:
// Either the child process has already completed;
// in that case we don't suspend and just receive the data
// Or the child has not completed yet;
// in that case we suspend here until it completes.
std::string data = co_await t;
}
Is there some trick to achieve this with lazy task<T>
? I couldn't think of anything.
Laziness is one of the biggest defects of future<T>
, and why I've never found them useful in any real program.
After researching for a while I wrote this implementation of detached spawning of coroutines quite easily, though I don't know whether it is really correct in every aspect...
namespace detail
{
struct spawn_t final
{
struct promise_type final
{
spawn_t get_return_object() const noexcept { return {}; }
std::suspend_never initial_suspend() const noexcept { return {}; }
std::suspend_never final_suspend() const noexcept { return {}; }
void return_void() const noexcept {}
void unhandled_exception() const noexcept(false) { std::terminate(); } // Unhandled exceptions should not slip through
};
};
}
template <coro::awaitable Awaitable>
detail::spawn_t spawn(Awaitable awaitable) { co_await awaitable; }
That coro::awaitable
is just the Awaitable
concept as described in cppcoro
.
Hello, the above example by @Chlorie works fine -- but yeah as @stepan-pieshkin said we might need a std::async
for task<T>
to avoid things become too forgetful (miss the chance to handle exceptions properly, etc)
I have implemented that variant of std::async
for myself, naming it co_async
, here's the code:
class fire_and_forget {
public:
class promise_type {
public:
fire_and_forget get_return_object() noexcept { return {}; }
auto initial_suspend() noexcept { return suspend_never{}; }
auto final_suspend() noexcept { return suspend_never{}; }
void return_void() noexcept {}
void unhandled_exception() noexcept {} // forgets the exception
void get() noexcept {}
};
};
template <typename T> inline future<T> co_async(task<T> tsk) noexcept {
promise<T> prom_;
auto fut = prom_.get_future();
([](promise<T> prom_, task<T> tsk) -> fire_and_forget {
try {
if constexpr (std::is_void_v<T>) {
co_await std::move(tsk);
prom_.set_value();
} else {
prom_.set_value(co_await std::move(tsk));
}
} catch(...) { prom_.set_exception(std::current_exception()); }
})(std::move(prom_), std::move(tsk));
return fut;
}
this will work with non-awaitable promise<T>
future<T>
impl, too. feel free to tell me if I made any mistakes
Found this undocumented async_scope
class, maybe this is the perferred way to start detached coroutines?
@Chlorie @omegacoleman Great work. How about adding these to cppcoro?
@Chlorie async_scope
is just great, IMO, it provides the desired "fire & forget" semantics, while enabling one to reap the exception(s) of the associated, one way, async operation(s)
Here is usage example of it, provided by the library author
cppcoro::task<void> do_something(T& resource, int i);
cppcoro::task<void> example1() {
T sharedResource;
cppcoro::async_scope scope;
for (int i = 0; i < 10; ++i) {
// Launch work on the current thread.
// Executes inline until and returns from spawn() once
// the coroutine reaches its first suspend point.
scope.spawn(do_something(sharedResource, i));
}
// Wait until all spawned work runs to completion
// before 'sharedResource' goes out of scope.
co_await scope.join();
}
seems async_scope
disables you to acquire the result.
seems
async_scope
disables you to acquire the result.
The use case is literally fire and forget so how would you want to acquire the result? If you really care about the result you could just make a coroutine function which awaits the task, gets the result, and uses it, while you spawn this wrapper function on the scope.
Also, cppcoro seems quite dead to me. I think the focus (of the library authors) has been transferred to the development of senders/receivers (P2300), which supports cancellation by design and contains a lot of algorithms, and also may be the direction to which future standardization goes.