Quelle est la différence entre packaged_task et async

En travaillant avec le modèle fileté de C ++ 11, j’ai remarqué que

std::packaged_task task([](int a, int b) { return a + b; }); auto f = task.get_future(); task(2,3); std::cout << f.get() << '\n'; 

et

 auto f = std::async(std::launch::async, [](int a, int b) { return a + b; }, 2, 3); std::cout << f.get() << '\n'; 

semble faire exactement la même chose. Je comprends qu’il pourrait y avoir une différence majeure si j’ai couru std::async avec std::launch::deferred , mais y en a-t-il une dans ce cas?

Quelle est la différence entre ces deux approches et, plus important encore, dans quel cas d’utilisation dois-je utiliser l’une ou l’autre?

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

Un packaged_task ne démarre pas seul, vous devez l’invoquer:

 std::packaged_task 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

En revanche, std::async avec launch::async essaiera d'exécuter la tâche dans un thread 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"; 

Inconvénient

Mais avant d’essayer d’utiliser async pour tout, gardez à l’esprit que le futur renvoyé a un état partagé spécial, qui exige que future::~future blocs future::~future :

 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 un réel asynchrone, vous devez conserver le future retourné, 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, consultez l'article async et ~future Herb Sutter, qui décrit le problème, et les std::futures de std::async Scott Meyer ne sont pas spéciaux , ce qui décrit les idées. Notez également que ce comportement a été spécifié dans C ++ 14 et les versions ultérieures , mais également généralement implémenté 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 peut être déplacé vers d'autres threads.

 std::packaged_task task(...); auto f = task.get_future(); std::thread myThread(std::move(task),2,3); std::cout << f.get() << "\n"; 

De plus, une f.get() packaged_task doit être appelée avant d’appeler f.get() , sinon votre programme se fige car le futur ne sera jamais prêt:

 std::packaged_task 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 ne vous souciez pas vraiment de leur finalisation, et std::packaged_task si vous voulez boucler les choses pour les déplacer vers d'autres threads ou les appeler plus tard. Ou, pour citer Christian :

À la fin, un std::packaged_task est juste une fonctionnalité de bas niveau pour implémenter std::async (c'est pourquoi il peut faire plus que std::async s'il est utilisé avec d'autres choses de bas niveau, comme std::thread ). Simplement dit, un std::packaged_task est un std::function lié à un std::future et std::async et appelle un std::packaged_task (éventuellement dans un thread différent).

“Le modèle de classe std :: packaged_task enveloppe toute cible appelable (fonction, expression lambda, expression de liaison ou autre object fonction) pour pouvoir être appelée de manière asynchrone. Sa valeur de retour ou son exception est stockée dans un état partagé accessible à travers std :: future objects. ”

“La fonction de modèle asynchrone exécute la fonction f de manière asynchrone (potentiellement dans un thread séparé) et renvoie un std :: future qui contiendra éventuellement le résultat de cet appel de fonction.”

Tâche emballée vs asynchrone

p> La tâche empaquetée contient une tâche [function or function object] et une paire avenir / promesse. Lorsque la tâche exécute une instruction return, la valeur set_value(..) sur la promesse de packaged_task .

a> Étant donné Future, promesse et tâche, nous pouvons créer des tâches simples sans trop nous soucier des threads [le thread est juste quelque chose que nous donnons pour exécuter une tâche].

Cependant, nous devons tenir compte du nombre de threads à utiliser ou de la meilleure exécution d’une tâche sur le thread en cours ou sur un autre, etc. Une telle séquence peut être gérée par un lanceur de thread appelé async() , qui décide de créer un nouveau thread ou recycler un ancien ou simplement exécuter la tâche sur le thread en cours. Il retourne un avenir.