C++ std::future and std::promise: A New Way of Asynchronous Operations

Hello everyone! Today we’re going to talk about std::future and std::promise in C++. With the rise of multi-core processors, asynchronous programming has become an important skill for developers to master. The std::future and std::promise introduced in the C++11 standard are great partners for handling asynchronous tasks, allowing us to complete asynchronous operations more elegantly.

In this article, I will explain the basic concepts of std::future and std::promise and their practical applications in asynchronous tasks using simple and understandable language. We will demonstrate step by step how they work together through code examples. Are you ready? Let’s get started!

1. What are std::future and std::promise?

1. std::future

std::future is an object used to obtain the result of an asynchronous operation. It can be understood as a placeholder for a “future value” that allows you to retrieve the result of a task at the appropriate time.

For example, if you send a thread to run, and you sit in a chair waiting for it to finish and tell you the result, that “result” is std::future.

2. std::promise

std::promise is an object used to set the “future value”. It is responsible for passing the result to the associated std::future.

Continuing with the previous analogy, the runner is std::promise, and he will eventually provide a “run completed” result to let you (std::future) know.

The Relationship Between Them

std::promise and std::future are good partners:

  • std::promise sets the value.
  • std::future gets the value.

Their working mechanism is as follows:

  1. You create a std::promise object.
  2. You obtain an associated std::future object through the std::promise::get_future() method.
  3. In another thread, std::promise sets the value, and std::future is responsible for asynchronously obtaining this value.

2. Code Example: Basic Usage of std::future and std::promise

Let’s look at a simple example to see how they work.

Example: Calculating the Square of a Number

#include <iostream>
#include <thread>
#include <future> // Include std::future and std::promise

// Asynchronous task: calculate square
void calculateSquare(std::promise<int> promise, int value) {
    int result = value * value;
    std::this_thread::sleep_for(std::chrono::seconds(2)); // Simulate time-consuming operation
    promise.set_value(result); // Set the calculated result in promise
}

int main() {
    std::promise<int> promise; // Create promise
    std::future<int> future = promise.get_future(); // Get the associated future with promise

    int value = 5;
    std::thread t(calculateSquare, std::move(promise), value); // Start thread, passing promise

    std::cout << "Calculating the square of " << value << "..." << std::endl;

    int result = future.get(); // Get the result of the asynchronous operation (blocking wait)
    std::cout << "Result is: " << result << std::endl;

    t.join(); // Wait for thread to finish
    return 0;
}

Output Result

Calculating the square of 5...
Result is: 25

Code Interpretation

  1. **Create std::promise**: std::promise<int> promise is used to store the calculation result.
  2. **Get std::future**: Get the associated future object through promise.get_future().
  3. Asynchronous Task: In the asynchronous thread, call promise.set_value() to set the result.
  4. Synchronous Result Retrieval: The main thread retrieves the asynchronous result through future.get(). This call is blocking until the asynchronous task completes.

3. std::async: Simplifying Asynchronous Operations

C++ provides a simpler tool—**std::async**, which can automatically create threads for us and return a std::future object. This way, we do not need to manually create std::promise and threads.

Example: Using std::async to Calculate Square

#include <iostream>
#include <future> // Include std::async and std::future

// Asynchronous task: calculate square
int calculateSquare(int value) {
    std::this_thread::sleep_for(std::chrono::seconds(2)); // Simulate time-consuming operation
    return value * value;
}

int main() {
    int value = 5;

    // Use std::async to start asynchronous task
    std::future<int> future = std::async(std::launch::async, calculateSquare, value);

    std::cout << "Calculating the square of " << value << "..." << std::endl;

    int result = future.get(); // Get the result of the asynchronous operation (blocking wait)
    std::cout << "Result is: " << result << std::endl;

    return 0;
}

Output Result

Calculating the square of 5...
Result is: 25

Advantages of std::async

  • Simplified Code: No need to manually create std::promise and threads.
  • Convenient and Flexible: Directly returns std::future, allowing us to easily obtain results.

4. Common Methods of std::future

std::future provides some convenient methods to handle asynchronous results:

  1. get() blocks waiting for the asynchronous task to complete and returns the result.

  2. wait() blocks waiting for the task to complete but does not return the result.

  3. wait_for() waits for a specified time, returning a timeout status if it exceeds the time limit.

Example: Using wait_for

#include <iostream>
#include <future>
#include <chrono>

int main() {
    // Asynchronous task
    std::future<int> future = std::async(std::launch::async, [] {
        std::this_thread::sleep_for(std::chrono::seconds(2));
        return 42;
    });

    std::cout << "Waiting for task to complete..." << std::endl;

    // Wait for up to 1 second
    if (future.wait_for(std::chrono::seconds(1)) == std::future_status::timeout) {
        std::cout << "Task timed out!" << std::endl;
    } else {
        std::cout << "Task completed, result is: " << future.get() << std::endl;
    }

    return 0;
}

Output Result

Waiting for task to complete...
Task timed out!

5. Precautions and Tips

  1. future.get() is a one-time operation: After calling get(), the result of future is taken away, and calling it again will throw an exception.
  2. Avoid Deadlocks: When using std::promise and std::future, ensure that threads can finish normally, otherwise it may lead to deadlocks.
  3. Delay Start of std::async: By default, std::async may delay the execution of tasks. You can force immediate start by using std::launch::async.

6. Exercises

  1. Modify the first example code to calculate the sum of two numbers in the thread and return the result to the main thread.
  2. Use std::async to implement a program that simulates the time-consuming operation of downloading a file.
  3. Use std::future::wait_for to check if a task has timed out and return a default value in case of timeout.

Conclusion

Through this article, we learned about std::future and std::promise in C++, understood how they work together to achieve asynchronous operations, and saw how std::async simplifies the writing of asynchronous tasks. Mastering these tools will allow you to handle asynchronous tasks more confidently in multithreaded programming.

That’s all for today’s C++ learning journey! Remember to try the code, and feel free to ask me any questions in the comments section. I wish you all success in your learning, and may your C++ skills improve continuously! See you next time! 😊

Leave a Comment