Criar operações assíncronas em C++ para aplicativos UWP
Este documento descreve alguns dos principais pontos a serem considerados quando você usa a classe de tarefa para produzir operações assíncronas baseadas no Windows ThreadPool em um aplicativo UWP (Universal Windows Runtime).
O uso da programação assíncrona é um componente-chave no modelo de aplicativo do Windows Runtime porque permite que os aplicativos permaneçam responsivos à entrada de usuário. Você pode iniciar uma tarefa de longa execução sem bloquear o thread da IU e pode receber os resultados da tarefa mais tarde. Você também pode cancelar tarefas e receber notificações de progresso à medida que as tarefas são executadas em segundo plano. O documento programação assíncrona no C++ fornece uma visão geral do padrão assíncrono disponível no Visual C++ para criar aplicativos do UWP. Esse documento ensina como consumir e criar cadeias de operações assíncronas do Windows Runtime. Esta seção descreve como usar os tipos em ppltasks.h para produzir operações assíncronas que podem ser consumidas por outro componente do Windows Runtime e como controlar como o trabalho assíncrono é executado. Leia também Padrões e dicas de programação assíncrona no Hilo (aplicativos da Microsoft Store usando C++ e XAML) para saber como usamos a classe de tarefa para implementar operações assíncronas no Hilo, um aplicativo do Windows Runtime usando C++ e XAML.
Observação
Você pode usar a PPL (Biblioteca de Padrões Paralelos) e a Biblioteca de Agentes Assíncronos em um aplicativo UWP. No entanto, você não pode usar o Agendador de Tarefas ou o Resource Manager. Este documento descreve recursos adicionais que a PPL fornece que estão disponíveis apenas para um aplicativo UWP e não para um aplicativo da área de trabalho.
Pontos-chave
Use concurrency::create_async para criar operações assíncronas que podem ser usadas por outros componentes (que podem ser escritas em linguagens diferentes de C++).
Use concurrency::progress_reporter para relatar notificações de progresso para componentes que chamam suas operações assíncronas.
Use tokens de cancelamento para habilitar operações assíncronas internas a serem canceladas.
O comportamento da função
create_async
depende do tipo de retorno da função de trabalho que é passada para ela. Uma função de trabalho que retorna uma tarefa (task<T>
outask<void>
) é executada de modo síncrono no contexto chamadocreate_async
. Uma função de trabalho que retornaT
ouvoid
é executada em um contexto arbitrário.Você pode usar o método concurrency::task::then para criar uma cadeia de tarefas que executam uma após a outra. Em um aplicativo UWP, o contexto padrão para as continuações de uma tarefa depende de como essa tarefa foi construída. Se a tarefa foi criada passando uma ação assíncrona para o construtor de tarefas ou passando uma expressão lambda que retorna uma ação assíncrona, o contexto padrão para todas as continuações dessa tarefa é o contexto atual. Se a tarefa não for construída usando uma ação assíncrona, um contexto arbitrário será usado por padrão para as continuações da tarefa. Você pode substituir o contexto padrão pela classe concurrency::task_continuation_context.
Neste documento
Criando operações assíncronas
Você pode usar o modelo de tarefa e continuação na PPL (Biblioteca de Padrões Paralelos) para definir tarefas em segundo plano, bem como tarefas adicionais executadas quando a tarefa anterior for concluída. Essa funcionalidade é fornecida pela classe concurrency::task. Para obter mais informações sobre esse modelo e a classe task
, confira Paralelismo de Tarefa.
O Windows Runtime é uma interface de programação que pode ser usada para criar aplicativos UWP que são executados somente em um ambiente de sistema operacional especial. Esses aplicativos usam funções autorizadas, tipos de dados e dispositivos, sendo distribuídos usando a Microsoft Store. O Windows Runtime é representado pela ABI (Interface Binária de Aplicativo). A ABI é um contrato binário subjacente que torna as APIs do Windows Runtime disponíveis para as linguagens de programação como Visual C++.
Usando o Windows Runtime, você pode usar os melhores recursos de várias linguagens de programação e combiná-las em um aplicativo. Por exemplo, você pode criar sua interface do usuário em JavaScript e executar a lógica de aplicativo com uso intensivo de computação em um componente C++. A capacidade de execução dessas operações com uso intensivo de computação em segundo plano é um fator-chave para manter a interface do usuário responsiva. Como a classe task
é específica do C++, você deve usar uma interface do Windows Runtime para comunicar operações assíncronas a outros componentes (que podem ser escritos em linguagens diferentes do C++). O Windows Runtime fornece quatro interfaces que você pode usar para representar operações assíncronas:
Windows::Foundation::IAsyncAction
Representa uma ação assíncrona.
Windows::Foundation::IAsyncActionWithProgress<TProgress>
Representa uma ação assíncrona que relata o progresso.
Windows::Foundation::IAsyncOperation<TResult>
Representa uma ação assíncrona que retorna um resultado.
Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>
Representa uma operação assíncrona que retorna um resultado e relata o progresso.
A noção de uma ação significa que a tarefa assíncrona não produz um valor (pense em uma função que retorna void
). A noção de uma operação significa que a tarefa assíncrona produz um valor. A noção de progresso significa que a tarefa pode relatar mensagens de progresso ao chamador. O JavaScript, o .NET Framework e o Visual C++ fornecem sua própria maneira de criar instâncias dessas interfaces para uso no limite da ABI. Para o Visual C++, a PPL fornece a função concurrency::create_async. Essa função cria uma ação ou operação assíncrona do Windows Runtime que representa a conclusão de uma tarefa. A função create_async
usa uma função de trabalho (normalmente uma expressão lambda), cria internamente um objeto task
e encapsula essa tarefa em uma das quatro interfaces assíncronas do Windows Runtime.
Observação
Use create_async
somente quando precisar criar uma funcionalidade que possa ser acessada de outra linguagem ou de outro componente do Windows Runtime. Use a classe task
diretamente quando souber que a operação é produzida e consumida pelo código C++ no mesmo componente.
O tipo de retorno create_async
é determinado pelo tipo de seus argumentos. Por exemplo, se sua função de trabalho não retornar um valor e não relatar o progresso, create_async
retornará IAsyncAction
. Por exemplo, se sua função de trabalho não retornar um valor e relatar o progresso, create_async
retornará IAsyncActionWithProgress
. Para relatar o progresso, forneça um objeto concurrency::progress_reporter como o parâmetro para sua função de trabalho. A capacidade de relatar o progresso permite que você relate qual quantidade de trabalho foi executada e qual quantidade ainda permanece (por exemplo, como percentual). Ela também permite que você relate os resultados à medida que eles se tornam disponíveis.
As interfaces IAsyncAction
, IAsyncActionWithProgress<TProgress>
, IAsyncOperation<TResult>
e IAsyncActionOperationWithProgress<TProgress, TProgress>
fornecem um método Cancel
que permite cancelar a operação assíncrona. A classe task
funciona com tokens de cancelamento. Quando você usa um token de cancelamento para cancelar o trabalho, o runtime não inicia um novo trabalho que assina esse token. O trabalho que já está ativo pode monitorar seu token de cancelamento e parar quando puder. Esse mecanismo é descrito com mais detalhes no documento Cancelamento na PPL. Você pode conectar o cancelamento de tarefa com os métodos Cancel
do Windows Runtime de duas maneiras. Em primeiro lugar, é possível definir a função de trabalho que você transmite a create_async
para usar um objeto concurrency::cancellation_token. Quando o método Cancel
é chamado, esse token de cancelamento é cancelado e as regras de cancelamento normais se aplicam ao objeto task
subjacente que dá suporte à chamada create_async
. Se você não fornecer um objeto cancellation_token
, o objeto task
subjacente define um de forma implícita. Defina um objeto cancellation_token
quando você precisa responder de forma cooperativa ao cancelamento na sua função de trabalho. A seção Exemplo: controlar a execução em um aplicativo do Windows Runtime com C++ e XAML mostra um exemplo de como executar o cancelamento em um aplicativo UWP (Plataforma Universal do Windows) com C# e XAML que usa um componente C++ do Windows Runtime personalizado.
Aviso
Em uma cadeia de continuações de tarefa, sempre limpe o estado e chame concurrency::cancel_current_task quando o token de cancelamento for cancelado. Se você retornar mais cedo em vez de chamar cancel_current_task
, a operação fará a transição para o estado concluído em vez de para o estado cancelado.
A tabela a seguir resume as combinações que você pode usar para definir operações assíncronas em seu aplicativo.
Para criar essa interface do Windows Runtime | Retorne esse tipo de create_async |
Passe esses tipos de parâmetro para sua função de trabalho para usar um token de cancelamento implícito | Passe esses tipos de parâmetro para sua função de trabalho para usar um token de cancelamento explícito |
---|---|---|---|
IAsyncAction |
void ou task<void> |
(nenhum) | (cancellation_token ) |
IAsyncActionWithProgress<TProgress> |
void ou task<void> |
(progress_reporter ) |
(progress_reporter , cancellation_token ) |
IAsyncOperation<TResult> |
T ou task<T> |
(nenhum) | (cancellation_token ) |
IAsyncActionOperationWithProgress<TProgress, TProgress> |
T ou task<T> |
(progress_reporter ) |
(progress_reporter , cancellation_token ) |
Você pode retornar um valor ou um objeto task
da função de trabalho que você passa para a função create_async
. Essas variações produzem comportamentos diferentes. Quando você retorna um valor, a função de trabalho é encapsulada em um task
para que ela possa ser executada em um thread em segundo plano. Além disso, o task
subjacente usa um token de cancelamento implícito. Por outro lado, se você retornar um objeto task
, a função de trabalho será executada de modo síncrono. Portanto, se você retornar um objeto task
, verifique se todas as operações demoradas em sua função de trabalho também são executadas como tarefas para permitir que seu aplicativo permaneça responsivo. Além disso, o task
subjacente não usa um token de cancelamento implícito. Portanto, você precisa definir sua função de trabalho para pegar um objeto cancellation_token
se precisar de suporte para cancelamento ao retornar um objeto task
de create_async
.
O exemplo a seguir mostra as várias maneiras de criar um objeto IAsyncAction
que pode ser consumido por outro componente do Windows Runtime.
// Creates an IAsyncAction object and uses an implicit cancellation token.
auto op1 = create_async([]
{
// Define work here.
});
// Creates an IAsyncAction object and uses no cancellation token.
auto op2 = create_async([]
{
return create_task([]
{
// Define work here.
});
});
// Creates an IAsyncAction object and uses an explicit cancellation token.
auto op3 = create_async([](cancellation_token ct)
{
// Define work here.
});
// Creates an IAsyncAction object that runs another task and also uses an explicit cancellation token.
auto op4 = create_async([](cancellation_token ct)
{
return create_task([ct]()
{
// Define work here.
});
});
Exemplo: Criando um componente do Tempo de Execução do Windows em C++ e consumindo-o a partir de C#
Considere um aplicativo que usa XAML e C# para definir a interface do usuário e um componente do Windows Runtime C++ para executar operações com uso intensivo de computação. Neste exemplo, o componente C++ calcula quais números em um determinado intervalo são primos. Para ilustrar as diferenças entre as quatro interfaces de tarefa assíncrona do Windows Runtime, inicie, no Visual Studio, criando uma solução em branco e nomeando-a como Primes
. Em seguida, adicione à solução um projeto do componente do Windows Runtime e nomeie-o como PrimesLibrary
. Adicione o código a seguir ao arquivo de cabeçalho C++ gerado (este exemplo renomeia Class1.h como Primes.h). Cada método public
define uma das quatro interfaces assíncronas. Os métodos que retornam um valor retornam um objeto Windows::Foundation::Collections::IVector<int>. Os métodos que relatam o progresso produzem valores double
que definem o percentual de trabalho geral concluído.
#pragma once
namespace PrimesLibrary
{
public ref class Primes sealed
{
public:
Primes();
// Computes the numbers that are prime in the provided range and stores them in an internal variable.
Windows::Foundation::IAsyncAction^ ComputePrimesAsync(int first, int last);
// Computes the numbers that are prime in the provided range and stores them in an internal variable.
// This version also reports progress messages.
Windows::Foundation::IAsyncActionWithProgress<double>^ ComputePrimesWithProgressAsync(int first, int last);
// Gets the numbers that are prime in the provided range.
Windows::Foundation::IAsyncOperation<Windows::Foundation::Collections::IVector<int>^>^ GetPrimesAsync(int first, int last);
// Gets the numbers that are prime in the provided range. This version also reports progress messages.
Windows::Foundation::IAsyncOperationWithProgress<Windows::Foundation::Collections::IVector<int>^, double>^ GetPrimesWithProgressAsync(int first, int last);
};
}
Observação
Por convenção, nomes de método assíncrono no Windows Runtime normalmente terminam com "Async".
Adicione o código a seguir ao arquivo de origem C++ gerado (este exemplo renomeia Class1.cpp como Primes.cpp). A função is_prime
determina se sua entrada é um número primo. Os métodos restantes implementam a classe Primes
. Cada chamada para create_async
usar uma assinatura compatível com o método do qual ela é chamada. Por exemplo, como Primes::ComputePrimesAsync
retorna IAsyncAction
, a função de trabalho fornecida a create_async
não retorna um valor e não usa um objeto progress_reporter
como parâmetro.
// PrimesLibrary.cpp
#include "pch.h"
#include "Primes.h"
#include <atomic>
#include <collection.h>
#include <ppltasks.h>
#include <concurrent_vector.h>
using namespace concurrency;
using namespace std;
using namespace Platform;
using namespace Platform::Collections;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace PrimesLibrary;
Primes::Primes()
{
}
// Determines whether the input value is prime.
bool is_prime(int n)
{
if (n < 2)
{
return false;
}
for (int i = 2; i < n; ++i)
{
if ((n % i) == 0)
{
return false;
}
}
return true;
}
// Adds the numbers that are prime in the provided range
// to the primes global variable.
IAsyncAction^ Primes::ComputePrimesAsync(int first, int last)
{
return create_async([this, first, last]
{
// Ensure that the input values are in range.
if (first < 0 || last < 0)
{
throw ref new InvalidArgumentException();
}
// Perform the computation in parallel.
parallel_for(first, last + 1, [this](int n)
{
if (is_prime(n))
{
// Perhaps store the value somewhere...
}
});
});
}
IAsyncActionWithProgress<double>^ Primes::ComputePrimesWithProgressAsync(int first, int last)
{
return create_async([first, last](progress_reporter<double> reporter)
{
// Ensure that the input values are in range.
if (first < 0 || last < 0)
{
throw ref new InvalidArgumentException();
}
// Perform the computation in parallel.
atomic<long> operation = 0;
long range = last - first + 1;
double lastPercent = 0.0;
parallel_for(first, last + 1, [&operation, range, &lastPercent, reporter](int n)
{
// Report progress message.
double progress = 100.0 * (++operation) / range;
if (progress >= lastPercent)
{
reporter.report(progress);
lastPercent += 1.0;
}
if (is_prime(n))
{
// Perhaps store the value somewhere...
}
});
reporter.report(100.0);
});
}
IAsyncOperation<IVector<int>^>^ Primes::GetPrimesAsync(int first, int last)
{
return create_async([this, first, last]() -> IVector<int>^
{
// Ensure that the input values are in range.
if (first < 0 || last < 0)
{
throw ref new InvalidArgumentException();
}
// Perform the computation in parallel.
concurrent_vector<int> primes;
parallel_for(first, last + 1, [this, &primes](int n)
{
// If the value is prime, add it to the global vector.
if (is_prime(n))
{
primes.push_back(n);
}
});
// Sort the results.
sort(begin(primes), end(primes), less<int>());
// Copy the results to an IVector object. The IVector
// interface makes collections of data available to other
// Windows Runtime components.
auto results = ref new Vector<int>();
for (int prime : primes)
{
results->Append(prime);
}
return results;
});
}
IAsyncOperationWithProgress<IVector<int>^, double>^ Primes::GetPrimesWithProgressAsync(int first, int last)
{
return create_async([this, first, last](progress_reporter<double> reporter) -> IVector<int>^
{
// Ensure that the input values are in range.
if (first < 0 || last < 0)
{
throw ref new InvalidArgumentException();
}
// Perform the computation in parallel.
concurrent_vector<int> primes;
long operation = 0;
long range = last - first + 1;
double lastPercent = 0.0;
parallel_for(first, last + 1, [&primes, &operation, range, &lastPercent, reporter](int n)
{
// Report progress message.
double progress = 100.0 * (++operation) / range;
if (progress >= lastPercent)
{
reporter.report(progress);
lastPercent += 1.0;
}
// If the value is prime, add it to the local vector.
if (is_prime(n))
{
primes.push_back(n);
}
});
reporter.report(100.0);
// Sort the results.
sort(begin(primes), end(primes), less<int>());
// Copy the results to an IVector object. The IVector
// interface makes collections of data available to other
// Windows Runtime components.
auto results = ref new Vector<int>();
for (int prime : primes)
{
results->Append(prime);
}
return results;
});
}
Cada método executa, em primeiro lugar, a validação para garantir que os parâmetros de entrada não sejam negativos. Se um valor de entrada for negativo, o método lançará Platform::InvalidArgumentException. O tratamento de erro é explicado posteriormente nesta seção.
Para consumir esses métodos de um aplicativo UWP, use o modelo Aplicativo em branco (XAML) do Visual C# para adicionar um segundo projeto à solução do Visual Studio. Este exemplo nomeia o projeto Primes
. Em seguida, no projeto Primes
, adicione uma referência ao projeto PrimesLibrary
.
Adicione o código a seguir ao MainPage.xaml. Esse código define a interface do usuário para que você possa chamar o componente C++ e exibir resultados.
<Page
x:Class="Primes.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Primes"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="300"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="125"/>
<RowDefinition Height="125"/>
<RowDefinition Height="125"/>
</Grid.RowDefinitions>
<StackPanel Grid.Column="0" Grid.Row="0">
<Button Name="b1" Click="computePrimes">Compute Primes</Button>
<TextBlock Name="tb1"></TextBlock>
</StackPanel>
<StackPanel Grid.Column="1" Grid.Row="0">
<Button Name="b2" Click="computePrimesWithProgress">Compute Primes with Progress</Button>
<ProgressBar Name="pb1" HorizontalAlignment="Left" Width="100"></ProgressBar>
<TextBlock Name="tb2"></TextBlock>
</StackPanel>
<StackPanel Grid.Column="0" Grid.Row="1">
<Button Name="b3" Click="getPrimes">Get Primes</Button>
<TextBlock Name="tb3"></TextBlock>
</StackPanel>
<StackPanel Grid.Column="1" Grid.Row="1">
<Button Name="b4" Click="getPrimesWithProgress">Get Primes with Progress</Button>
<ProgressBar Name="pb4" HorizontalAlignment="Left" Width="100"></ProgressBar>
<TextBlock Name="tb4"></TextBlock>
</StackPanel>
<StackPanel Grid.Column="0" Grid.Row="2">
<Button Name="b5" Click="getPrimesHandleErrors">Get Primes and Handle Errors</Button>
<ProgressBar Name="pb5" HorizontalAlignment="Left" Width="100"></ProgressBar>
<TextBlock Name="tb5"></TextBlock>
</StackPanel>
<StackPanel Grid.Column="1" Grid.Row="2">
<Button Name="b6" Click="getPrimesCancellation">Get Primes with Cancellation</Button>
<Button Name="cancelButton" Click="cancelGetPrimes" IsEnabled="false">Cancel</Button>
<ProgressBar Name="pb6" HorizontalAlignment="Left" Width="100"></ProgressBar>
<TextBlock Name="tb6"></TextBlock>
</StackPanel>
</Grid>
</Page>
Adicione o código a seguir à classe MainPage
no MainPage.xaml. Esse código define um objeto Primes
e os manipuladores de eventos de botão.
private PrimesLibrary.Primes primesLib = new PrimesLibrary.Primes();
private async void computePrimes(object sender, RoutedEventArgs e)
{
b1.IsEnabled = false;
tb1.Text = "Working...";
var asyncAction = primesLib.ComputePrimesAsync(0, 100000);
await asyncAction;
tb1.Text = "Done";
b1.IsEnabled = true;
}
private async void computePrimesWithProgress(object sender, RoutedEventArgs e)
{
b2.IsEnabled = false;
tb2.Text = "Working...";
var asyncAction = primesLib.ComputePrimesWithProgressAsync(0, 100000);
asyncAction.Progress = new AsyncActionProgressHandler<double>((action, progress) =>
{
pb1.Value = progress;
});
await asyncAction;
tb2.Text = "Done";
b2.IsEnabled = true;
}
private async void getPrimes(object sender, RoutedEventArgs e)
{
b3.IsEnabled = false;
tb3.Text = "Working...";
var asyncOperation = primesLib.GetPrimesAsync(0, 100000);
await asyncOperation;
tb3.Text = "Found " + asyncOperation.GetResults().Count + " primes";
b3.IsEnabled = true;
}
private async void getPrimesWithProgress(object sender, RoutedEventArgs e)
{
b4.IsEnabled = false;
tb4.Text = "Working...";
var asyncOperation = primesLib.GetPrimesWithProgressAsync(0, 100000);
asyncOperation.Progress = new AsyncOperationProgressHandler<IList<int>, double>((operation, progress) =>
{
pb4.Value = progress;
});
await asyncOperation;
tb4.Text = "Found " + asyncOperation.GetResults().Count + " primes";
b4.IsEnabled = true;
}
private async void getPrimesHandleErrors(object sender, RoutedEventArgs e)
{
b5.IsEnabled = false;
tb5.Text = "Working...";
var asyncOperation = primesLib.GetPrimesWithProgressAsync(-1000, 100000);
asyncOperation.Progress = new AsyncOperationProgressHandler<IList<int>, double>((operation, progress) =>
{
pb5.Value = progress;
});
try
{
await asyncOperation;
tb5.Text = "Found " + asyncOperation.GetResults().Count + " primes";
}
catch (ArgumentException ex)
{
tb5.Text = "ERROR: " + ex.Message;
}
b5.IsEnabled = true;
}
private IAsyncOperationWithProgress<IList<int>, double> asyncCancelableOperation;
private async void getPrimesCancellation(object sender, RoutedEventArgs e)
{
b6.IsEnabled = false;
cancelButton.IsEnabled = true;
tb6.Text = "Working...";
asyncCancelableOperation = primesLib.GetPrimesWithProgressAsync(0, 200000);
asyncCancelableOperation.Progress = new AsyncOperationProgressHandler<IList<int>, double>((operation, progress) =>
{
pb6.Value = progress;
});
try
{
await asyncCancelableOperation;
tb6.Text = "Found " + asyncCancelableOperation.GetResults().Count + " primes";
}
catch (System.Threading.Tasks.TaskCanceledException)
{
tb6.Text = "Operation canceled";
}
b6.IsEnabled = true;
cancelButton.IsEnabled = false;
}
private void cancelGetPrimes(object sender, RoutedEventArgs e)
{
cancelButton.IsEnabled = false;
asyncCancelableOperation.Cancel();
}
Esses métodos usam as palavras-chave async
e await
para atualizar a interface do usuário após a conclusão das operações assíncronas. Para obter informações sobre codificação assíncrona em aplicativos UWP, confira Threading e programação assíncrona.
Os métodos getPrimesCancellation
e cancelGetPrimes
trabalham juntos para permitir que o usuário cancele a operação. Quando o usuário escolhe o botão Cancelar, o método cancelGetPrimes
chama IAsyncOperationWithProgress<TResult, TProgress>::Cancel para cancelar a operação. O Runtime de Simultaneidade, que gerencia a operação assíncrona subjacente, gera um tipo de exceção interna que é capturada pelo Windows Runtime para comunicar que o cancelamento foi concluído. Para saber mais sobre o modelo de cancelamento, confira Cancelamento.
Importante
Para permitir que a PPL relate corretamente ao Windows Runtime que cancelou a operação, não pegue esse tipo de exceção interna. Isso significa que você também não deve capturar todas as exceções (catch (...)
). Se você precisar capturar todas as exceções, gere novamente a exceção para garantir que o Windows Runtime possa concluir a operação de cancelamento.
A ilustração a seguir mostra o aplicativo Primes
depois que cada opção foi escolhida.
Para obter um exemplo que usa create_async
para criar tarefas assíncronas que podem ser consumidas por outras linguagens, confira Usar C++ no exemplo do Trip Optimizer do Bing Mapas.
Controlando o thread de execução
O Windows Runtime usa o modelo de threading COM. Nesse modelo, os objetos são hospedados em apartments diferentes, dependendo de como eles lidam com a sincronização. Os objetos thread-safe são hospedados no MTA (apartment com vários threads). Os objetos que devem ser acessados por um único thread são hospedados em um STA (apartment de thread único).
Em um aplicativo que tem uma interface do usuário, o thread ASTA (Application STA) é responsável pelo bombeamento de mensagens de janela e é o único thread no processo que pode atualizar os controles de interface do usuário hospedados pelo STA. Isso tem duas consequências. Em primeiro lugar, para permitir que o aplicativo permaneça responsivo, todas as operações de E/S e de uso intensivo de CPU não devem ser executadas no thread do ASTA. Em segundo lugar, os resultados provenientes de threads em segundo plano devem voltar para o ASTA por marshal para atualizar a interface do usuário. Em um aplicativo UWP C++, MainPage
e outras páginas XAML são executadas no ATSA. Portanto, as continuações de tarefa declaradas no ASTA são executadas lá por padrão para que você possa atualizar controles diretamente no corpo da continuação. No entanto, se você aninhar uma tarefa em outra, as continuações nessa tarefa aninhada serão executadas no MTA. Portanto, você precisa considerar se deseja especificar explicitamente em qual contexto essas continuações são executadas.
Uma tarefa criada de uma operação assíncrona, como IAsyncOperation<TResult>
, usa semântica especial que pode ajudar você a ignorar os detalhes do threading. Embora uma operação possa ser executada em um thread em segundo plano (ou pode não ser apoiada por um thread), suas continuações são, por padrão, garantidas para execução no apartment que iniciou as operações de continuação (ou seja, do apartment chamado task::then
). Você pode usar a classe concurrency::task_continuation_context para controlar o contexto de execução de uma continuação. Use esses métodos auxiliares estáticos para criar objetos task_continuation_context
:
Use concurrency::task_continuation_context::use_arbitrary para especificar que a continuação é executada em um thread em segundo plano.
Use concurrency::task_continuation_context::use_current para especificar que a continuação é executada em um thread que chamou
task::then
.
Você pode passar um objeto task_continuation_context
para o método task::then para controlar explicitamente o contexto de execução da continuação ou passar a tarefa para outro apartment e, em seguida, chamar o método task::then
para controlar implicitamente o contexto de execução.
Importante
Como o thread principal da IU dos aplicativos UWP é executado em STA, as continuações criadas nesse STA por padrão são executadas no STA. Assim, as continuações criadas no MTA são executadas no MTA.
A seção a seguir mostra um aplicativo que lê um arquivo do disco, localiza as palavras mais comuns nesse arquivo e mostra os resultados na interface do usuário. A operação final, atualizando a interface do usuário, ocorre no thread da IU.
Importante
Esse comportamento é específico para aplicativos UWP. Para aplicativos da área de trabalho, você não controla o local no qual as continuações são executadas. Em vez disso, o agendador escolhe um thread de trabalho no qual executar cada continuação.
Importante
Não chame concurrency::task::wait no corpo de uma continuação que é executada no STA. Caso contrário, o runtime gerará concurrency::invalid_operation porque esse método bloqueia o thread atual e pode fazer com que o aplicativo fique sem resposta. No entanto, você pode chamar o método concurrency::task::get para receber o resultado da tarefa antecedente em uma continuação baseada em tarefa.
Exemplo: controlar a execução em um aplicativo do Windows Runtime com C++ e XAML
Considere um aplicativo C++ XAML que lê um arquivo do disco, localiza as palavras mais comuns nesse arquivo e mostra os resultados na interface do usuário. Para criar esse aplicativo, inicie, no Visual Studio, criando um projeto de Aplicativo em branco (Universal do Windows) e nomeando-o como CommonWords
. No manifesto do aplicativo, especifique a funcionalidade Biblioteca de Documentos para permitir que o aplicativo acesse a pasta Documentos. Adicione também o tipo de arquivo de texto (.txt) à seção Declarações do manifesto do aplicativo. Para obter mais informações sobre as funcionalidades e declarações do aplicativo, confira Empacotamento, implantação e consulta de aplicativos do Windows.
Atualize o elemento Grid
em MainPage.xaml para incluir um elemento ProgressRing
e um elemento TextBlock
. O ProgressRing
indica que a operação está em andamento e TextBlock
mostra os resultados da computação.
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<ProgressRing x:Name="Progress"/>
<TextBlock x:Name="Results" FontSize="16"/>
</Grid>
Adicione as seguintes instruções #include
em pch.h.
#include <sstream>
#include <ppltasks.h>
#include <concurrent_unordered_map.h>
Adicione as declarações de método a seguir à classe MainPage
(MainPage.h).
private:
// Splits the provided text string into individual words.
concurrency::task<std::vector<std::wstring>> MakeWordList(Platform::String^ text);
// Finds the most common words that are at least the provided minimum length.
concurrency::task<std::vector<std::pair<std::wstring, size_t>>> FindCommonWords(const std::vector<std::wstring>& words, size_t min_length, size_t count);
// Shows the most common words on the UI.
void ShowResults(const std::vector<std::pair<std::wstring, size_t>>& commonWords);
Adicione as seguintes instruções using
em MainPage.cpp.
using namespace concurrency;
using namespace std;
using namespace Windows::Storage;
using namespace Windows::Storage::Streams;
Em MainPage.cpp, implemente os métodos MainPage::MakeWordList
, MainPage::FindCommonWords
e MainPage::ShowResults
. MainPage::MakeWordList
e MainPage::FindCommonWords
executam operações com uso intensivo de computação. O método MainPage::ShowResults
exibe o resultado da computação na interface do usuário.
// Splits the provided text string into individual words.
task<vector<wstring>> MainPage::MakeWordList(String^ text)
{
return create_task([text]() -> vector<wstring>
{
vector<wstring> words;
// Add continuous sequences of alphanumeric characters to the string vector.
wstring current_word;
for (wchar_t ch : text)
{
if (!iswalnum(ch))
{
if (current_word.length() > 0)
{
words.push_back(current_word);
current_word.clear();
}
}
else
{
current_word += ch;
}
}
return words;
});
}
// Finds the most common words that are at least the provided minimum length.
task<vector<pair<wstring, size_t>>> MainPage::FindCommonWords(const vector<wstring>& words, size_t min_length, size_t count)
{
return create_task([words, min_length, count]() -> vector<pair<wstring, size_t>>
{
typedef pair<wstring, size_t> pair;
// Counts the occurrences of each word.
concurrent_unordered_map<wstring, size_t> counts;
parallel_for_each(begin(words), end(words), [&counts, min_length](const wstring& word)
{
// Increment the count of words that are at least the minimum length.
if (word.length() >= min_length)
{
// Increment the count.
InterlockedIncrement(&counts[word]);
}
});
// Copy the contents of the map to a vector and sort the vector by the number of occurrences of each word.
vector<pair> wordvector;
copy(begin(counts), end(counts), back_inserter(wordvector));
sort(begin(wordvector), end(wordvector), [](const pair& x, const pair& y)
{
return x.second > y.second;
});
size_t size = min(wordvector.size(), count);
wordvector.erase(begin(wordvector) + size, end(wordvector));
return wordvector;
});
}
// Shows the most common words on the UI.
void MainPage::ShowResults(const vector<pair<wstring, size_t>>& commonWords)
{
wstringstream ss;
ss << "The most common words that have five or more letters are:";
for (auto commonWord : commonWords)
{
ss << endl << commonWord.first << L" (" << commonWord.second << L')';
}
// Update the UI.
Results->Text = ref new String(ss.str().c_str());
}
Modifique o construtor MainPage
para criar uma cadeia de tarefas de continuação que exibe na interface do usuário as palavras comuns no livro Ilíada de Homero. As duas primeiras tarefas de continuação, que dividem o texto em palavras individuais e encontram palavras comuns, podem ser demoradas e, portanto, são explicitamente definidas para serem executadas em segundo plano. A tarefa de continuação final, que atualiza a interface do usuário, não especifica um contexto de continuação e, portanto, segue as regras de threading do apartment.
MainPage::MainPage()
{
InitializeComponent();
// To run this example, save the contents of http://www.gutenberg.org/files/6130/6130-0.txt to your Documents folder.
// Name the file "The Iliad.txt" and save it under UTF-8 encoding.
// Enable the progress ring.
Progress->IsActive = true;
// Find the most common words in the book "The Iliad".
// Get the file.
create_task(KnownFolders::DocumentsLibrary->GetFileAsync("The Iliad.txt")).then([](StorageFile^ file)
{
// Read the file text.
return FileIO::ReadTextAsync(file, UnicodeEncoding::Utf8);
// By default, all continuations from a Windows Runtime async operation run on the
// thread that calls task.then. Specify use_arbitrary to run this continuation
// on a background thread.
}, task_continuation_context::use_arbitrary()).then([this](String^ file)
{
// Create a word list from the text.
return MakeWordList(file);
// By default, all continuations from a Windows Runtime async operation run on the
// thread that calls task.then. Specify use_arbitrary to run this continuation
// on a background thread.
}, task_continuation_context::use_arbitrary()).then([this](vector<wstring> words)
{
// Find the most common words.
return FindCommonWords(words, 5, 9);
// By default, all continuations from a Windows Runtime async operation run on the
// thread that calls task.then. Specify use_arbitrary to run this continuation
// on a background thread.
}, task_continuation_context::use_arbitrary()).then([this](vector<pair<wstring, size_t>> commonWords)
{
// Stop the progress ring.
Progress->IsActive = false;
// Show the results.
ShowResults(commonWords);
// We don't specify a continuation context here because we want the continuation
// to run on the STA thread.
});
}
Observação
Esse exemplo demonstra como especificar contextos de execução e como redigir uma cadeia de continuações. Lembre-se de que, por padrão, uma tarefa criada de uma operação assíncrona executa suas continuações no apartment chamado task::then
. Portanto, esse exemplo usa task_continuation_context::use_arbitrary
para especificar que as operações que não envolvem a interface do usuário sejam executadas em um thread em segundo plano.
A ilustração a seguir mostra os resultados do aplicativo CommonWords
.
Nesse exemplo, é possível dar suporte ao cancelamento porque os objetos task
com suporte para create_async
usam um token de cancelamento implícito. Defina sua função de trabalho para tomar um objeto cancellation_token
se suas tarefas precisarem responder ao cancelamento de maneira cooperativa. Para saber mais sobre cancelamento, confira Cancelamento na PPL