Qual é a diferença entre packaged_task e async


134

Enquanto trabalhava com o modelo encadeado do C ++ 11, notei que

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

e

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

parece fazer exatamente a mesma coisa. Entendo que poderia haver uma grande diferença se eu corresse std::asynccom ela std::launch::deferred, mas existe alguma nesse caso?

Qual é a diferença entre essas duas abordagens e, mais importante, em quais casos de uso devo usar uma sobre a outra?

Respostas:


161

Na verdade, o exemplo que você deu mostra as diferenças se você usar uma função bastante longa, como

//! sleeps for one second and returns 1
auto sleep = [](){
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 1;
};

Tarefa empacotada

A packaged_tasknão inicia por si própria, você deve invocá-la:

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

Por outro lado, std::asynccom launch::asynctentará executar a tarefa em um thread diferente:

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";

Recua

Mas antes de tentar usar asyncpara tudo, tenha em mente que o futuro retornado tem um especial compartilhada estado, que exige que os future::~futureblocos:

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;
*/

Portanto, se você quiser real assíncrono, precisará manter o retorno future, ou se não se importar com o resultado, se as circunstâncias mudarem:

{
    auto pizza = std::async(get_pizza);
    /* ... */
    if(need_to_go)
        return;          // ~future will block
    else
       eat(pizza.get());
}   

Para mais informações sobre este assunto, veja o artigo de Herb Sutter asynce~future , que descreve o problema e Scott Meyer std::futuresde std::asyncnão são especiais , que descreve os insights. Observe também que esse comportamento foi especificado no C ++ 14 e posterior , mas também comumente implementado no C ++ 11.

Diferenças adicionais

Ao usar, std::asyncvocê não pode mais executar sua tarefa em um encadeamento específico, onde std::packaged_taskpode ser movido para outros encadeamentos.

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";

Além disso, é packaged_tasknecessário chamar antes de ligar f.get(), caso contrário, o programa será congelado, pois o futuro nunca ficará pronto:

std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::cout << f.get() << "\n"; // oops!
task(2,3);

TL; DR

Use std::asyncse você quiser fazer algumas coisas e realmente não se importa quando elas são feitas, e std::packaged_taskse você deseja agrupar as coisas para movê-las para outros segmentos ou chamá-las mais tarde. Ou, para citar Christian :

No final, a std::packaged_taské apenas um recurso de nível inferior para implementação std::async(e é por isso que ele pode fazer mais do que std::asyncse usado em conjunto com outras coisas de nível inferior, como std::thread). Simplesmente falado a std::packaged_taské um std::functionvinculado a std::futuree std::asyncenvolve e chama um std::packaged_task(possivelmente em um segmento diferente).


9
Você deve adicionar que o futuro retornado pelos blocos assíncronos na destruição (como se você chamasse get), enquanto o futuro retornado pelo packaged_task não.
precisa saber é o seguinte

22
No final, a std::packaged_taské apenas um recurso de nível inferior para implementação std::async(e é por isso que ele pode fazer mais do que std::asyncse usado em conjunto com outras coisas de nível inferior, como std::thread). Simplesmente falado a std::packaged_taské um std::functionvinculado a std::futuree std::asyncenvolve e chama um std::packaged_task(possivelmente em um segmento diferente).
Christian Rau

Estou fazendo algumas experiências no bloco ~ future (). Não pude replicar o efeito de bloqueio na destruição futura de objetos. Tudo funcionou de forma assíncrona. Estou usando o VS 2013 e quando inicio o async, usei std :: launch :: async. De alguma forma, o VC ++ "corrigiu" esse problema?
Frank Liu

1
@FrankLiu: Bem, o N3451 é uma proposta aceita, que (tanto quanto eu sei) foi para o C ++ 14. Dado que Herb trabalha na Microsoft, não me surpreenderia se esse recurso fosse implementado no VS2013. Um compilador que segue estritamente as regras do C ++ 11 ainda mostraria esse comportamento.
Zeta

1
@ Mikhail Esta resposta precede C ++ 14 e C ++ 17, então eu não tinha os padrões, mas apenas as propostas em mãos. Vou remover o parágrafo.
Zeta

1

Tarefa empacotada vs assíncrona

p> Tarefa empacotada mantém uma tarefa[function or function object]eumpar futuro / promessa. Quando a tarefa executa uma declaração de retorno, ela causaset_value(..)apackaged_taskpromessa da.

a> Dado futuro, tarefa de promessa e pacote, podemos criar tarefas simples sem nos preocuparmos muito com os threads [thread é apenas algo que damos para executar uma tarefa].

No entanto é preciso considerar quantas tópicos para usar ou se uma tarefa é melhor corrida no segmento atual ou em outro descisions etc.Such podem ser manipulados por um fio lançador de chamada async(), que decide se deseja criar um novo segmento uma ou reciclar um velho um ou simplesmente execute a tarefa no encadeamento atual. Retorna um futuro.


0

"O modelo de classe std :: packaged_task agrupa qualquer destino que possa ser chamado (função, expressão lambda, expressão de ligação ou outro objeto de função) para que possa ser chamado de forma assíncrona. Seu valor de retorno ou exceção lançada é armazenada em um estado compartilhado que pode ser acessado através de objetos std :: future ".

"A função de modelo assíncrona executa a função f de forma assíncrona (potencialmente em um thread separado) e retorna um std :: future que eventualmente conterá o resultado dessa chamada de função."

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.