The function template, std::async, allows us to run a function (possibly time consuming) asynchronously. The actual function, that needs to be executed asynchronously, is passed to this std::sync. The std::sync returns a future object immediately without waiting for the passed function to finish execution. When the function execution will start depends of the options passed to the std::async(). We’ discuss that later. The future object’s shared state is made ready when the passed function returns. The future owner (the consumer thread) can get the returned value by calling future::get().
std::async Example Program
#include <iostream>
#include <thread>
#include <future>
std::string time_consuming_fn(std::string in) {
// Waiting for 5 seconds.
std::this_thread::sleep_for(std::chrono::seconds(5));
return in + " - QnA Plus";
}
int main() {
std::future<std::string> fut = std::async(time_consuming_fn, "Async test");
auto consumer = std::thread([&fut](){
auto ret = fut.get();
std::cout << "The received value: " << ret << std::endl;
});
consumer.join();
return 0;
}
$ g++ -o test test.cpp
$ ./test
The received value: Async test - QnA Plus
We have a time consuming function, time_consuming_fn(), that waits for 5 seconds (for demonstration) before returning a string value. It returns the input string concatenating with ‘ – QnA Plus‘.
If we call this function directly, the caller thread will block until the function returns – at least 5 seconds in this case. But the caller can call the function without being blocked by passing the function to std::async().
The std::async() returns the future object, fut, immediately. This fut object is then passed to a consumer thread that wait for the value to be available by calling fut.get().
The future gets ready when the actual function, time_consuming_fn(), returns. The fut.get() returns the value that we can see from the output.
The Asynchronous Operation
Here we put addition logs to understand the behavior in detail.
#include <iostream>
#include <iomanip>
#include <thread>
#include <future>
std::string getthread() {
std::stringstream ss;
ss << std::hash<std::thread::id>{}(std::this_thread::get_id());
return " (td:" + ss.str() + ")";
}
std::string timestamp() {
std::stringstream ss;
const std::time_t now = std::time(nullptr);
ss << std::put_time(std::gmtime(&now), "%Y-%m-%d %H:%M:%S");
return ss.str();
}
std::string time_consuming_fn(std::string in) {
std::cout << timestamp() << getthread() << ": time_consuming_fn called." << std::endl;
// Waiting for 5 seconds.
std::this_thread::sleep_for(std::chrono::seconds(5));
return in + " - QnA Plus";
}
int main() {
std::cout << timestamp() << getthread() << ": std::sync called." << std::endl;
std::future<std::string> fut = std::async(time_consuming_fn, "Async test");
auto consumer = std::thread([&fut](){
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << timestamp() << getthread() << ": fur.get() called in consumer thread." << std::endl;
auto ret = fut.get();
std::cout << timestamp() << getthread() << ": value received: " << ret << std::endl;
});
consumer.join();
return 0;
}
2023-10-07 13:48:17 (td:1890520238081959631): std::sync called.
2023-10-07 13:48:17 (td:4746478829274221388): time_consuming_fn called.
2023-10-07 13:48:19 (td:1063142772364158458): fur.get() called in consumer thread.
2023-10-07 13:48:22 (td:1063142772364158458): value received: Async test - QnA Plus
From the first two lines of this output, we can see to important things – 1) the time_consuming_fn() starts its execution as soon as the std:async() is called. 2) the time_consuming_fn() executes in a different thread than the caller.
From the third line, we can see that the consumer starts in another thread – different from the caller (main) or the time_consuming_fn() threads. The consumer called the fut.get() after 2 seconds of std::async() call but it got the result of time_consuming_fn() in next 3 seconds. So, the value became ready after 5 seconds of the std::async() call.
Deferred Execution
Now change this line of the above program
std::future<std::string> fut = std::async(time_consuming_fn, "Async test");
to
std::future<std::string> fut = std::async(std::launch::deferred, time_consuming_fn, "Async test");
The std::launch::deferred option is passed to the std::async().
2023-10-07 13:53:35 (td:4456240314770963869): std::sync called.
2023-10-07 13:53:37 (td:12308423908382034278): fur.get() called in consumer thread.
2023-10-07 13:53:37 (td:12308423908382034278): time_consuming_fn called.
2023-10-07 13:53:42 (td:12308423908382034278): value received: Async test - QnA Plus
From this output we can see that the time_consuming_fn() does not start execution immediately after the std::async() call. After two seconds the consumer called fut.get(). The time_consuming_fn() starts execution right after the fut.get() call. Another important difference to notice here is that the time_consuming_fn() execution happens in consumer thread. In the previous case the consumer thread and the time_consuming_fn() thread were different.
Here the result became ready after 7 seconds after the std::async() call, i.e., 5 seconds after the fut.get() call.
So, in summary, in deferred mode:
- the actual function execution does not start immediately after the std::async() call. Execution start after the consumer calls the get() function of the future object returned by std::async().
- the function executes in the consumer thread – where the future::get() is called from.