Paralelismo de tarefa (tempo de execução de simultaneidade)
Este documento descreve as tarefas e os grupos de trabalho em tempo de execução de simultaneidade.Uma tarefa é uma unidade de trabalho que executa um trabalho específicos.Geralmente executa uma tarefa paralelamente a outras tarefas e pode ser decompor em adicional, mais mais aguçado, tarefas.Um grupo de trabalho organiza uma coleção de tarefas.
Use tarefas quando você escreve o código assíncrona e deseja para qualquer operação ocorre após a conclusão da operação assíncrona.Por exemplo, você pode usar uma tarefa ler de forma assíncrona de um arquivo e uma tarefa de continuação de linha, que é explicada posteriormente neste documento, processar os dados de depois que ele fica disponível.Por outro lado, grupos de tarefas de uso para decompr o trabalho paralelo em partes menores.Por exemplo, suponha que você tenha um algoritmo recursivo que dividir o restante em dois particiona trabalho.Você pode usar grupos de trabalho para executar simultaneamente, esses particiona e aguarde o trabalho típica para.
Dica |
---|
Quando você deseja aplicar paralelamente a mesma rotina para cada elemento de uma coleção, use um algoritmo paralelo, como concurrency::parallel_for, em vez de uma tarefa ou um grupo de trabalho.Para obter mais informações sobre os algoritmos paralelos, consulte Algoritmos paralelos. |
Chave
Quando você passa as variáveis a uma expressão lambda por referência, você deve garantir que o tempo de vida do persiste variável até que a tarefa termina.
Use tarefas (a classe) de concurrency::task quando você escreve o código assíncrono.
Use grupos de trabalho (como a classe de concurrency::task_group ou o algoritmo de concurrency::parallel_invoke ) quando você precisa decompr o trabalho paralelo em partes menores e esperar de nas partes menores para concluir.
Use o método de concurrency::task::then para criar continuações.Uma continuação é uma tarefa executado de forma assíncrona após outra tarefa terminem.Você pode conectar qualquer número de continuações para formar uma cadeia de trabalho assíncrono.
Continuação uma chave com base é sempre agendada para execução quando a tarefa antecedente concluir, mesmo quando a tarefa antecedente será cancelada ou gera uma exceção.
Use concurrency::when_all para criar uma tarefa que ela após cada membro de um conjunto de tarefas completa.Use concurrency::when_any para criar uma tarefa que ela após um membro de um conjunto de tarefas completa.
As tarefas e grupos de trabalho podem participar em mecanismo de cancelamento de PPL.Para obter mais informações, consulte Cancelar o PPL.
Para saber como o tempo de execução trata exceções que são geradas por tarefas e por grupos de trabalho, consulte Manipulação de exceção em tempo de execução de concorrência.
Neste documento
Usando expressões Lambda
A classe de tarefas
Tarefas de continuação de linha
- Baseado em continuações chave com base
Compondo tarefas
A função de when_all
A função when_any
Tardia tarefa de execução
Grupos de trabalho
Comparando o task_group ao structured_task_group
Exemplo
Programação robusta
Usando expressões Lambda
Expressões Lambda são uma maneira comum de definir o trabalho que é executado por tarefas e por grupos de trabalho devido à sua sintaxe sucinto.Aqui estão algumas dicas usá-los em:
Porque executam as tarefas normalmente em segmentos de plano de fundo, esteja ciente de vida útil de objeto em que você capture as variáveis em expressões lambda.Quando você capture uma variável por valor, uma cópia do variável é feita no corpo do método.Quando você capture por referência, uma cópia não é feita.Como consequência, certifique-se de que o tempo de vida de qualquer variável que você capture por referência durar mais do que a tarefa que usa ele.
Em geral, não capturar variáveis por que são atribuídos na pilha.Isso também significa que você não precisa capturar variáveis de membro de objetos que são atribuídos na pilha.
É explícito sobre variáveis que você capture em expressões lambda para ajudá-lo a identificar o que você está captura por valor e por referência.Por esse motivo não recomendamos que você use as opções de [=] ou de [&] para expressões lambda.
Um padrão comum é quando uma tarefa em uma cadeia a seguir atribui a uma variável, e outra tarefa ler a variável.Você não pode capturar o valor porque cada tarefa de continuação de linha guardararia uma cópia diferente de aquele variável.Para variáveis pilha alocados, você também não pode capturar por referência porque a variável não pode mais ser válido.
Para resolver esse problema, usar um ponteiro inteligente, como std::shared_ptr, para envolver a variável e passar o ponteiro inteligente por valor.Para fazê-lo, o objeto subjacente pode ser atribuído e a leitura e durar mais do que as tarefas que a usam.Use essa técnica mesmo quando a variável é um ponteiro ou um identificador ()^de após a um objeto de Tempo de Execução do Windows.Aqui está um exemplo básicas:
// lambda-task-lifetime.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
#include <string>
using namespace concurrency;
using namespace std;
task<wstring> write_to_string()
{
// Create a shared pointer to a string that is
// assigned to and read by multiple tasks.
// By using a shared pointer, the string outlives
// the tasks, which can run in the background after
// this function exits.
auto s = make_shared<wstring>(L"Value 1");
return create_task([s]
{
// Print the current value.
wcout << L"Current value: " << *s << endl;
// Assign to a new value.
*s = L"Value 2";
}).then([s]
{
// Print the current value.
wcout << L"Current value: " << *s << endl;
// Assign to a new value and return the string.
*s = L"Value 3";
return *s;
});
}
int wmain()
{
// Create a chain of tasks that work with a string.
auto t = write_to_string();
// Wait for the tasks to finish and print the result.
wcout << L"Final value: " << t.get() << endl;
}
/* Output:
Current value: Value 1
Current value: Value 2
Final value: Value 3
*/
Para obter mais informações sobre expressões lambda, consulte Expressões lambda C++.
Superior[]
A classe de tarefas
Você pode usar a classe de concurrency::task para compor tarefas em um conjunto de operações dependentes.Este modelo de composição é suportado pela noção de continuações.Uma continuação permite que o código seja executado quando o anterior, ou o antecedente, tarefa concluírem.O resultado da tarefa antecedente é passado como entrada a uma ou mais tarefas de continuação de linha.Quando uma tarefa antecedente, concluir todas as tarefas de continuação de linha que esperarem nela são agendadas para a execução.Cada tarefa de continuação de linha recebe uma cópia do resultado da tarefa antecedente.Por sua vez, essas tarefas a seguir podem também ser tarefas antecedentes para outras continuações, criando assim uma cadeia de tarefas.As continuações ajudam você a criar cadeias de arbitrário- comprimento das tarefas específicas que tem dependências entre eles.Além disso, uma tarefa pode participar em cancelar antes das tarefas que inicia ou uma maneira cooperativa quando executar.Para obter mais informações sobre este modelo cancelar, consulte Cancelar o PPL.
task é uma classe de modelo.O parâmetro de tipo T é o tipo do resultado que é gerado por tarefa.Esse tipo pode ser void se a tarefa não retorna um valor.T não pode usar o modificador de const .
Quando você cria uma tarefa, você fornece uma função de trabalho que executa o corpo da tarefa.Essa função de trabalho vem na forma de uma função lambda, um ponteiro de função, ou um objeto de função.Para aguardar que a tarefa termina sem obter o resultado, chame o método de concurrency::task::wait .O método de task::wait retorna um valor de concurrency::task_status que descreve se a tarefa esteve concluída ou cancelado.Para obter o resultado de tarefas, chame o método de concurrency::task::get .Este método chama task::wait para aguardar que a tarefa termina, e protege como consequência a execução do segmento atual até que o resultado está disponível.
O exemplo a seguir mostra como criar uma tarefa, esperar seu resultado, e exibir seu valor.Os exemplos nesta documentação usam funções lambda pois fornecem uma sintaxe mais sucinto.No entanto, você também pode usar ponteiros de função e objetos de função quando você usa tarefas.
// basic-task.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
// Create a task.
task<int> t([]()
{
return 42;
});
// In this example, you don't necessarily need to call wait() because
// the call to get() also waits for the result.
t.wait();
// Print the result.
wcout << t.get() << endl;
}
/* Output:
42
*/
A função de concurrency::create_task permite que você use a palavra-chave de auto em vez de declarar o tipo.Por exemplo, considere o seguinte código que cria e imprime a matriz de identidade:
// create-task.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <string>
#include <iostream>
#include <array>
using namespace concurrency;
using namespace std;
int wmain()
{
task<array<array<int, 10>, 10>> create_identity_matrix([]
{
array<array<int, 10>, 10> matrix;
int row = 0;
for_each(begin(matrix), end(matrix), [&row](array<int, 10>& matrixRow)
{
fill(begin(matrixRow), end(matrixRow), 0);
matrixRow[row] = 1;
row++;
});
return matrix;
});
auto print_matrix = create_identity_matrix.then([](array<array<int, 10>, 10> matrix)
{
for_each(begin(matrix), end(matrix), [](array<int, 10>& matrixRow)
{
wstring comma;
for_each(begin(matrixRow), end(matrixRow), [&comma](int n)
{
wcout << comma << n;
comma = L", ";
});
wcout << endl;
});
});
print_matrix.wait();
}
/* Output:
1, 0, 0, 0, 0, 0, 0, 0, 0, 0
0, 1, 0, 0, 0, 0, 0, 0, 0, 0
0, 0, 1, 0, 0, 0, 0, 0, 0, 0
0, 0, 0, 1, 0, 0, 0, 0, 0, 0
0, 0, 0, 0, 1, 0, 0, 0, 0, 0
0, 0, 0, 0, 0, 1, 0, 0, 0, 0
0, 0, 0, 0, 0, 0, 1, 0, 0, 0
0, 0, 0, 0, 0, 0, 0, 1, 0, 0
0, 0, 0, 0, 0, 0, 0, 0, 1, 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 1
*/
Você pode usar a função de create_task para criar a operação equivalente.
auto create_identity_matrix = create_task([]
{
array<array<int, 10>, 10> matrix;
int row = 0;
for_each(begin(matrix), end(matrix), [&row](array<int, 10>& matrixRow)
{
fill(begin(matrixRow), end(matrixRow), 0);
matrixRow[row] = 1;
row++;
});
return matrix;
});
Se uma exceção é lançada durante a execução de uma tarefa, lê os de tempo de execução que exceção na chamada subsequente a task::get ou a task::wait, ou a uma continuação chave com base.Para obter mais informações sobre o mecanismo de manipulação de exceção de tarefas, consulte Manipulação de exceção em tempo de execução de concorrência.
Para um exemplo que usa task, concurrency::task_completion_event, cancelamento, consulte Passo a passo: Conectando-se com tarefas e solicitação de HTTP de XML (IXHR2).(A classe de task_completion_event é descrita posteriormente neste documento.)
Dica |
---|
Para obter detalhes que são específicos para tarefas em aplicativos de Windows Store , consulte Asynchronous programming in C++ e Criando operações assíncronas n C++ para aplicativos da Windows Store. |
Superior[]
Tarefas de continuação de linha
Na programação assíncrona, é muito comum para uma operação assíncrona, na conclusão, chamar uma segunda operação e passar dados.Tradicionalmente, isso é feito usando métodos callback.Em tempo de execução de concorrência, a mesma funcionalidade é fornecida por tarefas de continuaçãode linha.Uma tarefa de continuação de linha (também conhecida apenas como uma continuação) é uma tarefa assíncrono que é chamada por outra tarefa, que é conhecida como o antecedente, quando o antecedente completa.Usando continuações, você pode:
Passar dados de antecedente a seguir.
Especificar as condições precisas em que a seguir é chamada ou não chamada.
Cancelar uma continuação ou antes que ele ou cooperativa quando executar.
Fornecer dicas sobre como continuação deve ser agendada.(Isso se aplica somente a Windows Store aplicativos.Para obter mais informações, consulte Criando operações assíncronas n C++ para aplicativos da Windows Store.)
Chamar mais continuações do mesmo antecedente.
Chamar uma continuação quando alguns ou todos os vários antecedentess concluírem.
Encadeamento continuações um após o outro a qualquer comprimento.
Use uma continuação para manipular exceções que são geradas pelo antecedente.
Esses recursos permitem que você execute uma ou mais tarefas quando a primeira tarefa termina.Por exemplo, você pode criar uma continuação que compactar um arquivo após a primeira tarefa o ler a partir do disco.
O exemplo a seguir altera anterior para usar o método de concurrency::task::then para agendar uma continuação que imprime o valor de antecedente tarefa quando está disponível.
// basic-continuation.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
auto t = create_task([]() -> int
{
return 42;
});
t.then([](int result)
{
wcout << result << endl;
}).wait();
// Alternatively, you can chain the tasks directly and
// eliminate the local variable.
/*create_task([]() -> int
{
return 42;
}).then([](int result)
{
wcout << result << endl;
}).wait();*/
}
/* Output:
42
*/
Você pode aninhar e encadear tarefas para qualquer comprimento.Uma tarefa também pode ter vários continuações.O exemplo a seguir ilustra uma cadeia básica de continuação de linha que aumenta o valor da tarefa anterior três vezes.
// continuation-chain.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
auto t = create_task([]() -> int
{
return 0;
});
// Create a lambda that increments its input value.
auto increment = [](int n) { return n + 1; };
// Run a chain of continuations and print the result.
int result = t.then(increment).then(increment).then(increment).get();
wcout << result << endl;
}
/* Output:
3
*/
Uma continuação também pode retornar outra tarefa.Se não houver nenhum cancelar, então essa tarefa é executada antes de continuar subsequente.Essa técnica é conhecida como desempacotar assíncrono.Desempacotar assíncrono é útil quando você deseja realizar o trabalho adicional em segundo plano, mas não deseja que a tarefa atual bloquear o segmento atual.(Isso é comum em aplicativos de Windows Store , onde as continuações podem executar no encadeamento de interface do usuário).O exemplo a seguir mostra três tarefas.A primeira tarefa retorna outra tarefa que seja executada antes que uma tarefa de continuação de linha.
// async-unwrapping.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
auto t = create_task([]()
{
wcout << L"Task A" << endl;
// Create an inner task that runs before any continuation
// of the outer task.
return create_task([]()
{
wcout << L"Task B" << endl;
});
});
// Run and wait for a continuation of the outer task.
t.then([]()
{
wcout << L"Task C" << endl;
}).wait();
}
/* Output:
Task A
Task B
Task C
*/
Importante |
---|
Quando uma continuação de uma tarefa retorna uma tarefa aninhada do tipo N, a tarefa resultante tem o tipo N, não task<N>, e termina quando a tarefa termina aninhada.Ou seja a seguir executa desempacotar de tarefa aninhada. |
Superior[]
- Baseado em continuações chave com base
Um determinado objeto de task cujo tipo de retorno é T, você pode fornecer um valor de tipo T ou task<T> a suas tarefas de continuação de linha.Uma continuação que usa o tipo T é conhecida como uma continuação valor base.Uma continuação valor base é agendada para execução quando a tarefa termina antecedente sem erro e não cancelado.Uma continuação que usa o tipo task<T> como seu parâmetro é conhecido como uma continuação chave com base.Continuação uma chave com base é sempre agendada para execução quando a tarefa antecedente concluir, mesmo quando a tarefa antecedente será cancelada ou gera uma exceção.Você pode chamar task::get para obter o resultado de tarefa antecedente.Se a tarefa antecedente foi cancelado, task::get gera concurrency::task_canceled.Se a tarefa antecedente apresentou uma exceção, os rethrows de task::get que exceção.Continuação uma chave com base não está marcada como cancelada quando sua tarefa antecedente é cancelada.
Superior[]
Compondo tarefas
Esta seção descreve as funções de concurrency::when_all e de concurrency::when_any , que podem ajudá-lo a composição várias tarefas implementar padrões comuns.
A função de when_all
A função de when_all gera uma tarefa que ela após um conjunto de tarefas completa.Essa função retorna um objeto de std::vector que contém o resultado de cada tarefa no dataset.O seguinte exemplo usa when_all básico para criar uma tarefa que representa a conclusão de três outras tarefas.
// join-tasks.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <array>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
// Start multiple tasks.
array<task<void>, 3> tasks =
{
create_task([] { wcout << L"Hello from taskA." << endl; }),
create_task([] { wcout << L"Hello from taskB." << endl; }),
create_task([] { wcout << L"Hello from taskC." << endl; })
};
auto joinTask = when_all(begin(tasks), end(tasks));
// Print a message from the joining thread.
wcout << L"Hello from the joining thread." << endl;
// Wait for the tasks to finish.
joinTask.wait();
}
/* Sample output:
Hello from the joining thread.
Hello from taskA.
Hello from taskC.
Hello from taskB.
*/
Observação |
---|
As tarefas que você passa a when_all devem ser uniformes.Ou seja devem qualquer retorno o mesmo tipo. |
Você também pode usar a sintaxe de && para gerar uma tarefa que ela após um conjunto de tarefas concluir, conforme mostrado no exemplo o seguir.
auto t = t1 && t2; // same as when_all
É comum usar uma continuação juntamente com when_all para executar a ação após um conjunto de tarefas completa.O exemplo a seguir altera anterior para imprimir a soma dos três tarefas que cada um gera um resultado de int .
// Start multiple tasks.
array<task<int>, 3> tasks =
{
create_task([]() -> int { return 88; }),
create_task([]() -> int { return 42; }),
create_task([]() -> int { return 99; })
};
auto joinTask = when_all(begin(tasks), end(tasks)).then([](vector<int> results)
{
wcout << L"The sum is "
<< accumulate(begin(results), end(results), 0)
<< L'.' << endl;
});
// Print a message from the joining thread.
wcout << L"Hello from the joining thread." << endl;
// Wait for the tasks to finish.
joinTask.wait();
/* Output:
Hello from the joining thread.
The sum is 229.
*/
Nesse exemplo, você pode também especificar task<vector<int>> para gerar uma continuação chave com base.
Cuidado |
---|
Se qualquer tarefa em um conjunto de tarefas é cancelada ou gera uma exceção, when_all imediatamente completa e não espera concluir as tarefas restantes.Se uma exceção é lançada, os rethrows em tempo de execução a exceção quando você chamar task::get ou task::wait no objeto de task que when_all retorna.Se um gera de mais de uma tarefa, o tempo de execução escolha um de eles.Como consequência, se um gera uma exceção, certifique-se que você espera concluir todas as tarefas. |
Superior[]
A função when_any
A função de when_any gera uma tarefa que termina quando a primeira tarefa em um conjunto de tarefas completa.Essa função retorna um objeto de std::pair que contém o resultado da tarefa concluída e de índice da tarefa no dataset.
A função de when_any é especialmente útil nas seguintes situações:
Operações redundantes.Considere um algoritmo ou uma operação que podem ser executados de várias maneiras.Você pode usar a função de when_any para selecionar a operação que termine o primeiro e cancelarem nas operações restantes.
Operações intercaladas.Você pode iniciar várias operações que todos devem concluir e usar a função de when_any para processar resultados desde que cada operação completa.Depois que uma operação for concluída, você pode iniciar uma ou mais tarefas adicionais.
Operações estranguladas.Você pode usar a função de when_any para estender o cenário anterior limitando o número de operações simultâneas.
Operações expirados.Você pode usar a função de when_any para selecionar entre uma ou mais tarefas e uma tarefa que ela após uma hora específica.
Como com when_all, é comum usar uma continuação que tem when_any para executar a ação quando o primeiro em um conjunto de tarefas concluírem.O seguinte exemplo usa when_any básico para criar uma tarefa que termina quando o primeiro outras tarefas de três completa.
// select-task.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <array>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
// Start multiple tasks.
array<task<int>, 3> tasks = {
create_task([]() -> int { return 88; }),
create_task([]() -> int { return 42; }),
create_task([]() -> int { return 99; })
};
// Select the first to finish.
when_any(begin(tasks), end(tasks)).then([](pair<int, size_t> result)
{
wcout << "First task to finish returns "
<< result.first
<< L" and has index "
<< result.second
<< L'.' << endl;
}).wait();
}
/* Sample output:
First task to finish returns 42 and has index 1.
*/
Nesse exemplo, você pode também especificar task<pair<int, size_t>> para gerar uma continuação chave com base.
Observação |
---|
Como com when_all, as tarefas que você passa a when_any devem qualquer retorno o mesmo tipo. |
Você também pode usar a sintaxe de || para gerar uma tarefa que ela após a primeira tarefa em um conjunto de tarefas terminar, conforme mostrado no exemplo o seguir.
auto t = t1 || t2; // same as when_any
Superior[]
Tardia tarefa de execução
Às vezes é necessário atrasar a execução de uma tarefa até que uma condição seja satisfeita, ou iniciar uma tarefa em resposta a um evento externo.Por exemplo, na programação assíncrona, você pode ter que iniciar uma tarefa em resposta a um evento de conclusão de E/S.
Duas maneiras para fazer isso é usar uma continuação ou iniciar uma tarefa e uma espera em um evento dentro da função de trabalho de tarefas.No entanto, há casos onde não é possível usar uma dessas técnicas.Por exemplo, para criar uma continuação, você deve ter a tarefa antecedente.No entanto, se você não tiver a tarefa antecedente, você pode criar um evento de conclusão de tarefa e uma cadeia posterior evento de conclusão que a tarefa antecedente quando fica disponível.Além disso, porque uma tarefa de espera também bloquear um segmento, você pode usar eventos de conclusão de tarefas para realizar o trabalho quando uma operação assíncrona concluir, e libera como consequência um segmento.
Ajuda da classe de concurrency::task_completion_event simplificam a composição de tarefas.Como a classe de task , o parâmetro de tipo T é o tipo do resultado que é gerado por tarefa.Esse tipo pode ser void se a tarefa não retorna um valor.T não pode usar o modificador de const .Normalmente, um objeto de task_completion_event é fornecido para um segmento ou a uma tarefa que o sinalizem quando o valor para ele fica disponível.Ao mesmo tempo, uma ou mais tarefas são definidas como ouvintes do evento.Quando o evento é definido, as tarefas de escuta completa e suas continuações são agendadas para executar.
Para um exemplo que usa task_completion_event para implementar uma tarefa que ela após um atraso, considera Como: criar uma tarefa é concluída após um atraso.
Superior[]
Grupos de trabalho
Um grupo de trabalho organiza uma coleção de tarefas.Tarefas de enviar de grupos de trabalho sobre a uma fila de ser roubada.O agendador remove as tarefas da fila e nas executa em recursos de computação disponíveis.Após adicionar tarefas para um grupo de trabalho, você pode esperar concluir todas as tarefas ou cancele as tarefas que ainda não seguir o iniciarão.
O PPL usa as classes de concurrency::task_group e de concurrency::structured_task_group para representar grupos de trabalho, e a classe de concurrency::task_handle para representar as tarefas que executam nesses grupos.A classe de task_handle encapsula o código que executa o trabalho.Como a classe de task , a função de trabalho vem na forma de uma função lambda, funções, ou objeto ponteiro de função.Você normalmente não precisará trabalhar com objetos de task_handle diretamente.Em vez disso, você passa funções de trabalho a um grupo de trabalho, e o grupo de trabalho cria e gerencia os objetos de task_handle .
O PPL divide grupos de trabalho nessas duas categorias: grupos de trabalho não estruturada e grupos de trabalho estruturados.O PPL usa a classe de task_group para representar grupos de trabalho não estruturada e a classe de structured_task_group para representar grupos de trabalho estruturados.
Importante |
---|
O PPL também define o algoritmo de concurrency::parallel_invoke , que usa a classe de structured_task_group para executar paralelamente um conjunto de tarefas.Porque o algoritmo de parallel_invoke tem uma sintaxe mais sucinto, recomendamos que você usa em vez da classe de structured_task_group quando possível.O tópico Algoritmos paralelos descreve parallel_invoke em mais detalhes. |
Use parallel_invoke quando você tem várias tarefas independentes que você deseja executar ao mesmo tempo, e você deve aguardar todas as tarefas complete antes de continuar.Essa técnica é geralmente conhecida como a bifurcação e associa ao paralelismo.Use task_group quando você tem várias tarefas independentes que você deseja executar ao mesmo tempo, mas você deseja aguardar concluir as tarefas mais tarde.Por exemplo, você pode adicionar tarefas para um objeto de task_group e esperar concluir as tarefas em outra função ou outro segmento.
Os grupos de trabalho suportam o conceito de cancelamento.Cancelar o permite que você para sinalizar a todas as tarefas ativas que você deseja cancelar a operação total.O também cancelar evita as tarefas que ainda não partiram de iniciar.Para obter mais informações sobre o botão, consulte Cancelar o PPL.
O runtime também fornece um modelo de manipulação de exceção que permite que você lançar uma exceção de uma tarefa e para tratar essa exceção quando você espera que o grupo de trabalho associado para concluir.Para obter mais informações sobre este modelo de manipulação de exceção, consulte Manipulação de exceção em tempo de execução de concorrência.
Superior[]
Comparando o task_group ao structured_task_group
Embora tenhamos recomendemos que você usa task_group ou parallel_invoke em vez da classe de structured_task_group , há casos em que você deseja usar structured_task_group, por exemplo, quando você escreve um algoritmo paralelo que executa um número de tarefas variável ou requer o suporte de para o botão.Esta seção explica as diferenças entre task_group e as classes de structured_task_group .
A classe de task_group é segura.Como consequência você pode adicionar tarefas para um objeto de task_group vários segmentos e esperar sobre ou cancelar um objeto de task_group de vários threads.A compilação e a destruição de um objeto de structured_task_group devem ocorrer no mesmo escopo lexicalmente.Além disso, quaisquer operações em um objeto de structured_task_group devem ocorrer no mesmo segmento.A exceção a essa regra é concurrency::structured_task_group::cancel dos métodos e de concurrency::structured_task_group::is_canceling .Uma tarefa filho pode chamar esses métodos para cancelar a qualquer momento o grupo de trabalho ou a verificação pai para o botão.
Você pode executar tarefas adicionais em um objeto de task_group depois de você chamar o método de concurrency::task_group::wait ou de concurrency::task_group::run_and_wait .Inversamente, você não pode executar tarefas adicionais em um objeto de structured_task_group depois de você chamar os métodos de concurrency::structured_task_group::wait ou de concurrency::structured_task_group::run_and_wait .
Porque a classe de structured_task_group se não sincroniza entre threads, tem menos sobrecarga de execução da classe de task_group .Como consequência, se o problema não requer que você agenda o trabalho de vários segmentos e você não pode usar o algoritmo de parallel_invoke , a classe de structured_task_group pode ajudar você a escrever um código mais de trabalho satisfatório.
Se você usar um objeto de structured_task_group dentro de outro objeto de structured_task_group , o objeto deve concluir interno e ser destruído antes que o objeto externo conclua.A classe de task_group não requer que os grupos de trabalho aninhados terminem antes que o grupo externo conclua.
Grupos de trabalho não estruturada de trabalho e grupos de trabalho estruturada com alças de tarefa de maneiras diferentes.Você pode passar funções de trabalho diretamente a um objeto de task_group ; o objeto de task_group criará e gerenciará o identificador de tarefas para você.A classe de structured_task_group requer que você gerenciar um objeto de task_handle para cada tarefa.Cada objeto de task_handle deve permanecer válido em todo o tempo de vida do objeto associado de structured_task_group .Use a função de concurrency::make_task para criar um objeto de task_handle , conforme mostrado no seguinte exemplo básicas:
// make-task-structure.cpp
// compile with: /EHsc
#include <ppl.h>
using namespace concurrency;
int wmain()
{
// Use the make_task function to define several tasks.
auto task1 = make_task([] { /*TODO: Define the task body.*/ });
auto task2 = make_task([] { /*TODO: Define the task body.*/ });
auto task3 = make_task([] { /*TODO: Define the task body.*/ });
// Create a structured task group and run the tasks concurrently.
structured_task_group tasks;
tasks.run(task1);
tasks.run(task2);
tasks.run_and_wait(task3);
}
Para gerenciar as alças de tarefas para casos onde você tem um número de tarefas variável, use uma rotina de pilha alocação como _malloca ou uma classe recipiente, como std::vector.
task_group e structured_task_group suportam cancelamento.Para obter mais informações sobre o botão, consulte Cancelar o PPL.
Superior[]
Exemplo
O seguinte exemplo básicas mostra como trabalhar com grupos de trabalho.Este exemplo usa o algoritmo de parallel_invoke para executar simultaneamente duas tarefas.Cada tarefa adiciona subelemento tarefas para um objeto de task_group .Observe que a classe de task_group permite várias tarefas para adicionar simultaneamente tarefas.
// using-task-groups.cpp
// compile with: /EHsc
#include <ppl.h>
#include <sstream>
#include <iostream>
using namespace concurrency;
using namespace std;
// Prints a message to the console.
template<typename T>
void print_message(T t)
{
wstringstream ss;
ss << L"Message from task: " << t << endl;
wcout << ss.str();
}
int wmain()
{
// A task_group object that can be used from multiple threads.
task_group tasks;
// Concurrently add several tasks to the task_group object.
parallel_invoke(
[&] {
// Add a few tasks to the task_group object.
tasks.run([] { print_message(L"Hello"); });
tasks.run([] { print_message(42); });
},
[&] {
// Add one additional task to the task_group object.
tasks.run([] { print_message(3.14); });
}
);
// Wait for all tasks to finish.
tasks.wait();
}
A seguir está a saída de exemplo para esse exemplo:
Porque o algoritmo de parallel_invoke executa tarefas simultaneamente, a ordem das mensagens de saída pode variar.
Para os exemplos completos que mostram como usar o algoritmo de parallel_invoke , consulte Como: Use o parallel_invoke para gravar uma rotina paralela de tipo e Como: use o parallel_invoke para executar operações paralelas.Para um exemplo completo que usa a classe de task_group para implementar futuros assíncronas, consulte Passo a passo: implementando futuros.
Superior[]
Programação robusta
Certifique-se de que você entende a função de manipulação de exceção e cancelar quando você usar tarefas, grupos de trabalho, e algoritmos paralelos.Por exemplo, em uma árvore de trabalho paralelo, uma tarefa que é cancelada evita tarefas filhos de execução.Isso pode causar problemas se uma das tarefas filhos executar uma operação que são importantes para seu aplicativo, como liberar um recurso.Além disso, se uma tarefa filhos gera uma exceção, a exceção pode se propagar através de um destrutor do objeto e causar comportamento indefinido em seu aplicativo.Para um exemplo que ilustram esses pontos, consulte a seção de Entenda como o efeito de manipulação de exceção e cancelar a destruição objetos nas práticas recomendadas no documento de biblioteca dos padrões de paralela.Para obter mais informações sobre modelos de cancelamento e de manipulação de exceção no PPL, consulte Cancelar o PPL e Manipulação de exceção em tempo de execução de concorrência.
Superior[]
Tópicos relacionados
Nome |
Descrição |
---|---|
Como: Use o parallel_invoke para gravar uma rotina paralela de tipo |
Mostra como usar o algoritmo de parallel_invoke para melhorar o desempenho do algoritmo de tipo bitonic. |
Como: use o parallel_invoke para executar operações paralelas |
Mostra como usar o algoritmo de parallel_invoke para melhorar o desempenho de um programa que executa várias operações em uma fonte de dados compartilhado. |
Mostra como usar task, cancellation_token_source, cancellation_token, e classes de task_completion_event para criar uma tarefa que ela após um atraso. |
|
Mostra como combinar a funcionalidade existente em tempo de execução de simultaneidade em algo que faz mais. |
|
Descreve o PPL, que fornece um modelo imperativo de programação para desenvolver aplicativos simultâneas. |