An asynchronous surprise from std::async

30 Jun 2020

This incident happened sometime ago. I was working on a project in C++ and there occurred a need to run a heavyweight task to run in an asynchronous way. My initial thought was to launch an std::thread and to detach it and not to worry about it further. This could be a naive way of doing it. But I was measuring the performance of a particular module and I wanted to offload the heavyduty task from the main thread. At that point, I suddenly remembered about the std::async method. So I googled and read about the std::async method. I wanted to ensure that the task is launched in a separate thread and so I decided to specifically mention the launch policy as std::launch::async. This launch policy would ensure that the task will be launched asynchronously in a separate thread. So my code looked somewhat like given below.

void critical_fn() {
std::async(std::launch::async, foo);

According to the documentation, this will run the foo function in a separate thread. Also as per the documentation, I could get the result of the task using an std::future object which is actually the return value of the std::async API. But since I was just interested in offloading the task in a separate thread, I didn't bother to capture the return value in an std::future object. But little did I know then that this would actually fail the whole intent of using the std::async API. So I finished my implementation and started testing with small loads and it didn't pose any issues. Later while we were testing with higher loads, my friend notified me that, that the critical_fn was taking a lot of time to finish. Later on further analysis, we found that the std::async will wait on 1) the get() of the returned future object 2) the destructor of the std::future object and thus failing the whole purpose of moving the task to a separate thread.

This was deeply perplexing and on further reading, we found that this is a specialized case for the std::future returned by the std::async API. Scott Meyers has written a detailed article on this in his blog. So if you are using std::async for running a task asynchronously and if the result is not caught using an std::future object, the std::async will block the execution at the point where the destructor of its returned std::future object is invoked. This was an interesting behaviour, which we missed in the initial testing of the feature. So be careful, if you are just trying to offload task using std::async API.

Read more,