std::async – Execute a Function Asynchronously

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.

Author: Srikanta

I write here to help the readers learn and understand computer programing, algorithms, networking, OS concepts etc. in a simple way. I have 20 years of working experience in computer networking and industrial automation.


If you also want to contribute, click here.

Leave a Reply

Your email address will not be published. Required fields are marked *

0
0
0
0
0
0