When to use promise over async or packaged_task?

std::async

std::async is a neat and easy way to get a std::future, but:

  • It does not always start a new thread; the enum value std::launch::async can be passed as the first argument to std::async in order to ensure that a new thread is created to execute the task specified by func, thus ensuring that func executes asynchronously.

      auto f = std::async( std::launch::async, func );
    
  • The destructor of std::future can block until the new thread completes.

      auto sleep = [](int s) { std::this_thread::sleep_for(std::chrono::seconds(s)); };
    
      {
          auto f = std::async( std::launch::async, sleep, 5 );
      }
    

Normally we expect that only .get() or .wait() blocks, but for a std::future returned from std::async, the destructor also may block, so be careful not to block your main thread just by forgetting about it.

  • If the std::future is stored in a temporary object, the std::async call will block at the point of the object's destruction, so the following block will take 10 seconds if you remove the auto f = initializations. It will only block for 5 seconds otherwise, because the two sleeps will be concurrent, with a wait for both to complete resulting from the destruction of the two objects at the end of the block:

      auto sleep = [](int s) { std::this_thread::sleep_for(std::chrono::seconds(s)); };
    
      {
          auto f1 = std::async( std::launch::async, sleep, 5 );
          auto f2 = std::async( std::launch::async, sleep, 5 );
      }
    

std::packaged_task

std::packaged_task by itself has nothing to do with threads: it is just a functor and a related std::future. Consider the following:

auto task = [](int i) {
   std::this_thread::sleep_for(std::chrono::seconds(5));
   return i+100;
};

std::packaged_task< int(int) > package{ task };
std::future<int> f = package.get_future();
package(1);
std::cout << f.get() << "\n";

Here we just run the task by package(1), and after it returns, f is ready so no blocking on .get().

There is a feature of std::packaged_task that makes it very useful for threads. Instead of just a function, you can initialize std::thread with a std::packaged_task which gives a really nice way of getting to the 'std::future'. Consider the following:

std::packaged_task< int(int) > package{ task };
std::future<int> f = package.get_future();
std::thread t { std::move(package), 5 };

std::cout << f.get() << "\n";       //block here until t finishes

t.join();

Because std::packaged_task is not copyable, you must move it to new thread with std::move.

std::promise

std::promise is a powerful mechanism. For example, you can pass a value to new thread without need of any additional synchronization.

auto task = [](std::future<int> i) {
    std::cout << i.get() << std::flush;
};

std::promise<int> p;
std::thread t{ task, p.get_future() };

std::this_thread::sleep_for(std::chrono::seconds(5));
p.set_value(5);

t.join();

New thread will wait for us on .get()


So, in general, answering your question:

  • Use std::async only for simple things, e.g. to make some call non-blocking, but bear in mind the comments on blocking above.

  • Use std::packaged_task to easily get to a std::future, and run it as a separate thread

      std::thread{ std::move(package), param }.detach();
    

or

    std::thread t { std::move(package), param };
  • Use std::promise when you need more control over the future.

See also std::shared_future and on passing exceptions between threads std::promise::set_exception