Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between packaged_task and async

Actually the example you just gave shows the differences if you use a rather long function, such as

//! sleeps for one second and returns 1
auto sleep = [](){
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 1;
};

Packaged task

A packaged_task won't start on it's own, you have to invoke it:

std::packaged_task<int()> task(sleep);

auto f = task.get_future();
task(); // invoke the function

// You have to wait until task returns. Since task calls sleep
// you will have to wait at least 1 second.
std::cout << "You can see this after 1 second\n";

// However, f.get() will be available, since task has already finished.
std::cout << f.get() << std::endl;

std::async

On the other hand, std::async with launch::async will try to run the task in a different thread:

auto f = std::async(std::launch::async, sleep);
std::cout << "You can see this immediately!\n";

// However, the value of the future will be available after sleep has finished
// so f.get() can block up to 1 second.
std::cout << f.get() << "This will be shown after a second!\n";

Drawback

But before you try to use async for everything, keep in mind that the returned future has a special shared state, which demands that future::~future blocks:

std::async(do_work1); // ~future blocks
std::async(do_work2); // ~future blocks

/* output: (assuming that do_work* log their progress)
    do_work1() started;
    do_work1() stopped;
    do_work2() started;
    do_work2() stopped;
*/

So if you want real asynchronous you need to keep the returned future, or if you don't care for the result if the circumstances change:

{
    auto pizza = std::async(get_pizza);
    /* ... */
    if(need_to_go)
        return;          // ~future will block
    else
       eat(pizza.get());
}   

For more information on this, see Herb Sutter's article async and ~future, which describes the problem, and Scott Meyer's std::futures from std::async aren't special, which describes the insights. Also do note that this behavior was specified in C++14 and up, but also commonly implemented in C++11.

Further differences

By using std::async you cannot run your task on a specific thread anymore, where std::packaged_task can be moved to other threads.

std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::thread myThread(std::move(task),2,3);

std::cout << f.get() << "\n";

Also, a packaged_task needs to be invoked before you call f.get(), otherwise you program will freeze as the future will never become ready:

std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::cout << f.get() << "\n"; // oops!
task(2,3);

TL;DR

Use std::async if you want some things done and don't really care when they're done, and std::packaged_task if you want to wrap up things in order to move them to other threads or call them later. Or, to quote Christian:

In the end a std::packaged_task is just a lower level feature for implementing std::async (which is why it can do more than std::async if used together with other lower level stuff, like std::thread). Simply spoken a std::packaged_task is a std::function linked to a std::future and std::async wraps and calls a std::packaged_task (possibly in a different thread).


Packaged Task vs async

p> Packaged task holds a task [function or function object] and future/promise pair. When the task executes a return statement, it causes set_value(..) on the packaged_task's promise.

a> Given Future, promise and package task we can create simple tasks without worrying too much about threads [thread is just something we give to run a task].

However we need to consider how many threads to use or whether a task is best run on the current thread or on another etc.Such descisions can be handled by a thread launcher called async(), that decides whether to create a new a thread or recycle an old one or simply run the task on the current thread. It returns a future .


TL;DR

std::packaged_task allows us to get the std::future "bounded" to some callable, and then control when and where this callable will be executed without the need of that future object.

std::async enables the first, but not the second. Namely, it allows us to get the future for some callable, but then, we have no control of its execution without that future object.

Practical example

Here is a practical example of a problem that can be solved with std::packaged_task but not with std::async.

Consider you want to implement a thread pool. It consists of a fixed number of worker threads and a shared queue. But shared queue of what? std::packaged_task is quite suitable here.

template <typename T>
class ThreadPool {
public:
  using task_type = std::packaged_task<T()>;

  std::future<T> enqueue(task_type task) {
      // could be passed by reference as well...
      // ...or implemented with perfect forwarding
    std::future<T> res = task.get_future();
    { std::lock_guard<std::mutex> lock(mutex_);
      tasks_.push(std::move(task));
    }
    cv_.notify_one();
    return res;
  }

  void worker() { 
    while (true) {  // supposed to be run forever for simplicity
      task_type task;
      { std::unique_lock<std::mutex> lock(mutex_);
        cv_.wait(lock, [this]{ return !this->tasks_.empty(); });
        task = std::move(tasks_.top());
        tasks_.pop();
      }
      task();
    }
  }
  ... // constructors, destructor,...
private:
  std::vector<std::thread> workers_;
  std::queue<task_type> tasks_;
  std::mutex mutex_;
  std::condition_variable cv_;
};

Such functionality cannot be implemented with std::async. We need to return an std::future from enqueue(). If we called std::async there (even with deferred policy) and return std::future, then we would have no option how to execute the callable in worker(). Note that you cannot create multiple futures for the same shared state (futures are non-copyable).