std::packaged_task

std::bind(function, argsā€¦) returns a function object that is the result of the given arguments to the given function.

What does the line std::packaged_task<int()> task(std::bind(f, 2, 11)); do in the following code?

1
2
3
4
5
6
7
8
9
10
11
// unique function to avoid disambiguating the std::pow overload set
int f(int x, int y) { return std::pow(x,y); }
 
int main()
{
   std::packaged_task<int()> task(std::bind(f, 2, 11));
   task(); 
   auto result = task.get_future();

   std::cout << "task_bind:\t" << result.get() << '\n';
}
Last edited on
https://en.cppreference.com/w/cpp/thread/packaged_task

Line 6: Create a task that will execute pow(2, 11) asynchronously (in a different thread).
Line 7: Start the task.
Line 8: Get the future object. A future (also known as a promise) is an object that allows waiting for and receiving a value that is being computed asynchronously.
Line 9: Wait for the future to be completed and get the result.
So if we want to spawn a task to be done asynchronously (in a different thread) we have two options:
std::packaged_task: if the task returns a value
std::jthread or std::packaged_task: if the task doesn't return anything
Right?

But we can use std::async(std::launch::async, ...) for both purposes. Not?
Last edited on
std::jthread and std::thread are lower-level objects than std::packaged_task. An std::packaged_task might be executed in a number of different ways, such as in a thread pool, or conceivably synchronously when the controlling thread waits on the future. Also, an std::packaged_task need not return anything:
1
2
3
4
int x = 0;
std::packaged_task<void()> task([&x](){ x = 42; });
task.get_future().wait();
assert(x == 42);


std::jthread is used to spawn an actual OS thread. It's a more explicit request. Usually creating a thread will be more costly than creating a task.

Using std::async is another possibility. An std::packaged_task provides somewhat more flexibility by allowing preparing the task and starting it at different times.
Here's another example of std::pachaged_task with no return value:
1
2
3
4
5
6
void h() { std::cout << "Called\n"; }
int main()
{
    std::packaged_task<void()> task(h);
    task(); 
}
Called
So here there're two threads, one as the main thread executing main() and another launched by the packaged_task executing h, right?
Last edited on
The task is executed asynchronously. Most of the time this means in another thread, but not necessarily. If the execution environment supports some asynchronous execution mechanism other than thread, it may use that to execute the task. Even if the task is indeed executed in a different thread, the thread may not be launched when the task is started. The task may run in a thread that had already been created and was waiting for more work.

This is why I said that std::packaged_task is higher-level. You just tell the runtime "do this in the background while I do other work. I don't care how you do it."
Great.

One more thing about std::async. I heard that std::async(std::launch::async, ...) callbacks can be synchronized to concurrently execute tasks when there's shared data with write operation. So std::async sounds like std::jthread, but at a higher level. Right?
synchronized to concurrently execute tasks when there's shared data with write operation
It's somewhat unclear what you mean here. You can synchronize asynchronous operations to prevent race conditions by using mutexes and other synchronization primitives. If there's a different way with std::async() (besides the future) I don't know what it is.
I haven't actually used std::async for shared data when there's writing operation. I just heard that. But such a purpose can be performed right the way you mentioned using those synchronization primitives, to me too.

So to sum up:
When there's writing to shared data, we can use std::jthread and std::async (and synchronization primitives), otherwise, all features can be used (std::jthread, std::pachaged_task, std::async) with no synchronization.

By the way, could I know what C++ standard do you follow? (C++23?)
std::packaged_task callbacks can also be synchronized with primitives.

I'm currently on C++17. I'm rather conservative when it comes to updating to newer standard versions because my main development environment is Visual Studio and because of the add-ons I need to use. I'm not always able to update to the latest version as soon as it comes out. Besides that, staying conservative gives me more flexibility when it comes to moving my code to a new platform. You never know when you might want to target some weird platform that's stuck on some old version of GCC.
Plus, there's been too few language changes since C++11. The vast majority have been library changes, and most of those have been available on Boost for *years*. I was using boost::shared_ptr as early as 2011.
I notice that being conservative and staying on old versions of C++ standard (to me 11 or 17 are old when programmers are being familiar with new C++26 features at times) is what you "and" a number of other great tutors of this forum do. This will certainly have a bad effect on the whole forum because new learners like to use new features instead of old ones for which there're good reasons (e.g., why deal with complicated SFINAE while we can use C++20 concepts and requires), such examples are not just a few ones.
This attitude of you tutors will make this spectacular forum (from which I took many advantages) obsolete. (I also see far less questions being asked on the forum on a daily basis compared to the past)
I hope you tutors change your mind.
with respect
Consider that perhaps us more experienced folks have good reasons for choosing the way we do, rather than simply being unable or reluctant to adapt to change. If those reasons don't apply to you then feel free to do otherwise, but please don't suggest that we act the way we do for reasons other than practicality.

Also, we're not tutors. If we can answer a question we may, and if not we won't. We lose nothing either way, beyond the time spent.
Does std::packaged_task have any good use-case when used synchronously?
What do you mean by "used synchronously"?
I meant in non-threaded code (when there's only one thread - main thread - in the code).
I'm still not sure what you mean. std::packaged_task will in just about every case result in code executing in a different thread, and even when that's not the case the execution will still be asynchronous.

The point of std::packaged_task and std::async is that you can request tasks to be completed asynchronously relative to the caller, so that completing those tasks doesn't block the caller thread. Probably the most common example is to asynchronously request information from a network resource while you process the result of the previous request:
1
2
3
4
5
6
7
auto data = async_request().wait();
auto future = async_request();
while (data.is_valid()){
    process(data);
    data = future.wait();
    future = async_request();
}
Another is to make multiple requests in parallel to make use of the server's parallel capacity:
1
2
3
4
5
std::vector<future> futures(64);
for (auto &f : futures)
    f = async_request(requests[i++]);
for (auto &f : futures)
    process(f.wait());

The vast majority of the stuff you'll do with asynchronous tasks will be something along those lines. If what you want to do fits into that mold, you should probably use either std::async or std::packaged_task.
Last edited on
Let's focus only on
std::packaged_task and
synchronous code
By synchronous code (although I mentioned above and not quite sure for me how clearer I can say that) I mean that when there's only one thread in the code (the main thread) and no other thread explicitly or implicitly.

An example can be a regular code:
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <vector>
int main() {

  std::vector v {1,2,3,4,5};
  for(int& i : v) i *=2;
                           // there's only the main thread in this code
  std::cout << "data doubled is: ";

  for(int i : v) std::cout << i << ' ';
}

PS: one important point about std::packaged_task is that it doesn't launch a new thread explicitly/implicitly by itself; it only packs the task to be called explicitly (like task(); in code below) or to be used in a new thread declared explicitly if asynchronous execution is required.

So even if we use std::packaged_task in the code above like below there's yet only one thread in the code:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <vector>
int main() {

  std::vector v {1,2,3,4,5};
  auto f {[&v]() { for(int& i : v) i *=2; } };
  std::packaged_task task{f};
  task();
                           // there's still only the main thread in this code
  std::cout << "data doubled is: ";

  for(int i : v) std::cout << i << ' ';
}

My purpose is to know how in a code including only the main thread can std::packaged_task be useful?

If any part of my question is unclear, please tell me to clarify it.
Last edited on
Huh. My bad, I misinterpreted the reference. I thought the operator()() overload executed the function asynchronously. Apparently std::packaged_task is just an std::function wrapper + std::future for synchronization.

To answer your question, no, if you're not going to add any asynchronicity just use std::function.
Right.
Thanks for your time and info.
Topic archived. No new replies allowed.