I'm currently working on class with some async tasks under the hood. In fact I need to delegate few tasks to asynchronous execution and be sure that all of them are finished before class was destroyed, I was thinking about await all the tasks until finish inside class destructor. While research existing approaches used inside std library of C++ language. I was curios about different ways of asynchronous implementation.
For example std::thread
doesn't wait anything inside destructor. Following code will fail with executable termination:
void sleep() {
std::this_thread::sleep_for(std::chrono::seconds(5));
}
void execute_thead() {
auto thread = std::thread(&sleep);
}
So you forced to call join()
for the thread you've created. Overall it's look more obvious to me, like: "Hey! You've created async task then be so kind and do manual await of it". But anyway throwing exception in case you forgot to call join()
or detach()
may be unexpected;
On other hand, let's look on std::async
. Following code will await result inside future destructor:
void execute_async() {
auto result = std::async(std::launch::async, &sleep);
}
It may be not obvious that code code will hang when going out of scope, while waiting till all tasks is complete. It may be hard to find actual bottleneck while refactor or optimizing the code. But advantage is that you don't need to care about unfinished tasks.
Be aware, here I'm not discussing about thread-based vs task-based strategy. Formally I'm asking if implicit await is better than explicit one and is it overall a good way to write asynchronous code.
std::thread::~thread
callsstd::terminate
, it doesn'tthrow
auto result = std::async(std::launch::async, &sleep);
in a destructor very counterintuitive, especially if you do nothing withresult
.std::thread t(foo); bar(); t.join();
. This code is broken ifbar()
throws an exception, because then,t.join()
is not called. However, ifstd::thread
automatically joined in the destructor (RAII), you would not have that problem. So an explicitjoin()
function makes it harder to ensure exception safety if you compare it to an implicit join in the destructor.