Ответ 1
Фактически приведенный вами пример показывает различия, если вы используете довольно длинную функцию, например
//! sleeps for one second and returns 1
auto sleep = [](){
std::this_thread::sleep_for(std::chrono::seconds(1));
return 1;
};
Упакованная задача
A packaged_task
не будет запускаться самостоятельно, вы должны вызвать его:
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
С другой стороны, std::async
с launch::async
попытается запустить задачу в другом потоке:
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";
Недостаток (только для С++ 11)
Но прежде чем пытаться использовать async
для всех, имейте в виду, что возвращенное будущее имеет специальное разделяемое состояние, которое требует, чтобы 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;
*/
Итак, если вам нужна настоящая асинхронность, вам нужно сохранить возвращенный future
, или если вам не нужен результат, если обстоятельства меняются:
{
auto pizza = std::async(get_pizza);
/* ... */
if(need_to_go)
return; // ~future will block
else
eat(pizza.get());
}
Для получения дополнительной информации об этом см. статью Herb Sutter async
и ~future
, в которой описывается проблема, а Скотт Мейер std::futures
from std::async
не являются специальными, в которых описываются идеи. Также обратите внимание, что это поведение специфично для С++ 11, в С++ 14 этот деструктор будет не блокировать в вызывающем потоке.
Дополнительные отличия
Используя std::async
, вы больше не сможете запускать свою задачу в определенном потоке, где std::packaged_task
можно переместить в другие потоки.
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";
Кроме того, перед вызовом f.get()
необходимо вызвать packaged_task
, иначе программа будет зависеть, поскольку будущее не будет готово:
std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::cout << f.get() << "\n"; // oops!
task(2,3);
TL; DR
Используйте std::async
, если вам нужно что-то сделать, и не важно, когда они будут сделаны, и std::packaged_task
, если вы хотите обернуть вещи, чтобы переместить их в другие потоки или вызвать их позже. Или, чтобы процитировать христианский:
В конце a
std::packaged_task
представляет собой лишь функцию нижнего уровня для реализацииstd::async
(поэтому он может делать больше, чемstd::async
, если использовать вместе с другими материалами более низкого уровня, напримерstd::thread
). Проще говоря,std::packaged_task
- этоstd::function
, связанный с обложкамиstd::future
иstd::async
и вызываетstd::packaged_task
(возможно, в другом потоке).