En fait, l'exemple que vous venez de donner montre les différences si vous utilisez une fonction assez longue, telle que
//! sleeps for one second and returns 1
auto sleep = [](){
std::this_thread::sleep_for(std::chrono::seconds(1));
return 1;
};
Tâche emballée
A packaged_task
ne démarre pas tout seul, vous devez l'invoquer :
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
D'un autre côté, std::async
con launch::async
essaiera d'exécuter la tâche dans un fil différent :
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
Mais avant d'essayer d'utiliser async
pour tout, gardez à l'esprit que le futur retourné a un état partagé spécial, qui exige que future::~future
blocs :
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;
*/
Donc si vous voulez du vrai asynchrone, vous devez garder le retour future
ou si vous ne vous souciez pas du résultat si les circonstances changent :
{
auto pizza = std::async(get_pizza);
/* ... */
if(need_to_go)
return; // ~future will block
else
eat(pizza.get());
}
Pour plus d'informations à ce sujet, voir l'article d'Herb Sutter (en anglais). async
y ~future
qui décrit le problème, et celui de Scott Meyer. std::futures
de std::async
ne sont pas spéciales qui décrit les idées. Notez également que ce comportement a été spécifié dans C++14 et plus mais aussi couramment mis en œuvre dans C++11.
Autres différences
En utilisant std::async
vous ne pouvez plus exécuter votre tâche sur un thread spécifique, où std::packaged_task
peuvent être déplacées vers d'autres fils.
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";
En outre, un packaged_task
doit être invoqué avant que vous n'appeliez f.get()
sinon votre programme se bloquera car le futur ne sera jamais prêt :
std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::cout << f.get() << "\n"; // oops!
task(2,3);
TL;DR
Utilisez std::async
si vous voulez que certaines choses soient faites et que vous ne vous souciez pas vraiment de savoir quand elles seront faites, et std::packaged_task
si vous voulez conclure des choses afin de les déplacer vers d'autres fils ou de les appeler plus tard. Ou, pour citer Christian :
Au final, un std::packaged_task
est juste une fonctionnalité de niveau inférieur pour la mise en œuvre de std::async
(c'est pourquoi il peut faire plus que std::async
s'il est utilisé avec d'autres éléments de niveau inférieur, tels que std::thread
). En clair, un std::packaged_task
est un std::function
lié à un std::future
y std::async
enveloppe et appelle un std::packaged_task
(éventuellement dans un autre fil).