The std::async is not the only way to associate a std::future with a task. Using the std::packaged_task is also a possible solution.

Packaged task

We would like to represent the result of the function

int complicated_computation(double, bool);

with a future using a packaged task. This is done in three steps.

Creation

First, we create a packaged task:

std::packaged_task< int(double, bool) > task(complicated_computation);

The constructor of the std::packaged_task requires a function (callable target) as an argument. The template parameter is the signature of the function. The signature is int(double, bool) because complicated_computation returns int and takes arguments of types double and bool.

Future

The packaged task has a member function .get_future() which returns a future.

std::future< int > future = task.get_future();

The template parameter of the std::future must be the same as the return type of the function which was the argument for the packaged task constructor. In our example, the template parameter must be int because complicated_computation (which returns int) was the argument for the task constructor.

Execution

The packaged task is a callable object. The task forwards the arguments to the supplied function.

task(2.5, true);

The upper call runs the complicated_computation with arguments (2.5, true) and stores the result of the function in the future.

We can pass the packaged task around and call it at a different place than where it was created. This is the benefit of packaged tasks: decoupling the creation of the future with the execution of the task.

Typical usage

The typical usage of a packaged task is:

  • creating the packaged task with a function,

  • retrieving the future from the packaged task,

  • passing the packaged task elsewhere,

  • invoking the packaged task.

Temperature tomorrow revisited

We will use a packaged task on the example from the previous article.

The story was: A couple, wife and husband, are going on a picnic tomorrow. The wife would like to know what will be the temperature of the weather. Therefore, she asks her husband to look it up.

The two functions from the last example

void make_break(int millisec);

int temperature();

stay the same. The make_break() stops current thread for given amount of milliseconds and the temperature() returns the temperature, which the husband looked up. The main function is different

int main()
{
    std::cout << "Wife:    Tomorrow, we are going on a picnic.\n" 
              << "         What will be the weather...\n" 
              << "         \"What will be the "
              << "temperature tomorrow?\"" << std::endl;
    
    std::packaged_task< int() > task(temperature);
    std::future<int> answer = task.get_future();
    std::thread thread(std::move(task));
    thread.detach();
    
    make_break(2);
    
    std::cout << "Wife:    I should pack for tomorrow." << std::endl;
    
    make_break(2);

    std::cout << "Wife:    Hopefully my husband can figure out the weather soon."
              << std::endl;
    
    int temp = answer.get();

    std::cout << "Wife:    Finally, tomorrow will be " << temp << "... Em...\n"
              << "         \"In which units is the answer?\"" 
              << std::endl;

    return 0;
}

The interesting part is where the constructor of std::package_task creates a packaged task with the temperature(). Then, the future answer is retrieved from the task. Afterwards, we pass the task to a separate thread. The packaged task is not copyable, therefore std::move moves it to the thread. The thread is detached and computes the result asynchronously.

If the temperature would have additional input arguments, they would be listed in the std::thread constructor.

The entire source code is available here. The output of the program is:

$ ./packagedTask
Wife:    Tomorrow, we are going on a picnic.
         What will be the weather...
         "What will be the temperature tomorrow?"
Husband: Hm, is the weather forecast in the newspaper?
         Eh, we don't have a newspaper at home...
Wife:    I should pack for tomorrow.
Husband: I will look it up on the internet!
Wife:    Hopefully my husband can figure out the weather soon.
Husband: Here it is, it says tomorrow will be 40.
Wife:    Finally, tomorrow will be 40... Em...
         "In which units is the answer?"

This is a possible usage of a packaged task but it is not a common one. Using std::async is a lot easier in this case. But it is useful to start exploring packaged tasks with a familiar example.

Summary

The std::packaged_task is another way to associate a task with a std::future – the future representation of the result of the task.

In the next article, we will give another example of usage of packaged tasks. It will take advantage of the fact that the execution of the task and the retrieval of the future from the packaged task is not so tightly connected as in std::async.

Links: