Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Importante
Este tópico apresenta os conceitos de coroutines e co_await, que recomendamos que você use em sua interface do usuário e em seus aplicativos que não são de interface do usuário. Para simplificar, a maioria dos exemplos de código neste tópico introdutório mostra projetos Aplicativo de Console do Windows (C++/WinRT). Os exemplos de código posteriores neste tópico usam corrotinas, mas, para conveniência, os exemplos do aplicativo de console também continuam usando a chamada de função get de bloqueio logo antes de sair, de modo que o aplicativo não saia antes de concluir a impressão da saída. Você não fará isso (chame a função get de bloqueio) em um thread da IU. Em vez disso, você usará a declaração co_await. As técnicas que você usará em seus aplicativos de interface do usuário são descritas no tópico Simultaneidade avançada e assíncrona.
Este tópico introdutório mostra algumas das maneiras pelas quais você pode criar e consumir Windows Runtime objetos assíncronos com C++/WinRT. Depois de ler este tópico, especialmente para técnicas que você usará em seus aplicativos de interface do usuário, consulte também simultaneidade avançada e assíncrona.
Operações assíncronas e funções "Async" do Windows Runtime
Qualquer API Windows Runtime que tenha o potencial de levar mais de 50 milissegundos para ser concluída é implementada como uma função assíncrona (com um nome que termina em "Assíncrono"). A implementação de uma função assíncrona inicia o trabalho em outro thread e retorna imediatamente com um objeto que representa a operação assíncrona. Quando a operação assíncrona é concluída, esse objeto retornado contém qualquer valor resultante do trabalho. O namespace Windows::Foundation Windows Runtime contém quatro tipos de objeto de operação assíncrona.
- IAsyncAction,
- IAsyncActionWithProgress<TProgress>,
- IAsyncOperation<TResult> e
- IAsyncOperationWithProgress<TResult, TProgress>.
Cada um desses tipos de operação assíncrona é projetado em um tipo correspondente no namespace winrt::Windows::Foundation C++/WinRT. C++/WinRT também contém um struct de adaptador de espera interno. Você não o usa diretamente, mas, graças a esse struct, você pode escrever uma co_await instrução para aguardar cooperativamente o resultado de qualquer função que retorne um desses tipos de operação assíncrona. E você pode criar suas próprias coroutinas que retornam esses tipos.
Um exemplo de uma função de Windows assíncrona é SyndicationClient::RetrieveFeedAsync, que retorna um objeto de operação assíncrono do tipo IAsyncOperationWithProgress<TResult, TProgress>.
Vamos examinar algumas maneiras — primeiro bloquear e, em seguida, não bloquear — de usar C++/WinRT para chamar uma API como essa. Apenas para ilustrar as ideias básicas, usaremos um projeto Windows Aplicativo de Console (C++/WinRT) nos próximos exemplos de código. Técnicas mais apropriadas para um aplicativo de interface do usuário são discutidas em simultaneidade avançada e assíncrona.
Bloquear a conversa de chamada
O exemplo de código a seguir recebe um objeto de operação assíncrona de RetrieveFeedAsync e chama get nesse objeto para bloquear a thread de chamada até que os resultados da operação assíncrona estejam disponíveis.
Se você quiser copiar-colar este exemplo diretamente no arquivo de código-fonte principal de um projeto do Windows Console Application (C++/WinRT), primeiro defina Não Usando Cabeçalhos Pré-compilados nas propriedades do projeto.
// main.cpp
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Web.Syndication.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
void ProcessFeed()
{
Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
SyndicationClient syndicationClient;
SyndicationFeed syndicationFeed{ syndicationClient.RetrieveFeedAsync(rssFeedUri).get() };
// use syndicationFeed.
}
int main()
{
winrt::init_apartment();
ProcessFeed();
}
Chamar get é conveniente para a codificação e é ideal para aplicativos de console ou threads de segundo plano nos quais você não deseja usar uma corrotina. Mas não é simultâneo nem assíncrono, portanto, não é apropriado para um thread de interface do usuário (e uma asserção será disparada em builds não otimizados se você tentar usá-lo em um). Para evitar que os threads do sistema operacional não executem outro trabalho útil, precisamos de uma técnica diferente.
Escrever uma coroutina
O C++/WinRT integra as coroutinas C++ ao modelo de programação para fornecer uma maneira natural de aguardar cooperativamente um resultado. Você pode produzir sua própria operação assíncrona do Windows Runtime criando uma corrotina. No exemplo de código abaixo, ProcessFeedAsync é a corrotina.
Note
A função get existe no tipo de projeção C++/WinRT winrt::Windows::Foundation::IAsyncAction, para que você possa chamar a função de dentro de qualquer projeto C++/WinRT. Você não encontrará a função listada como membro da interface IAsyncAction, pois get não faz parte da superfície da interface binária de aplicativo (ABI) do tipo real IAsyncAction do Windows Runtime.
// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
void PrintFeed(SyndicationFeed const& syndicationFeed)
{
for (SyndicationItem const& syndicationItem : syndicationFeed.Items())
{
std::wcout << syndicationItem.Title().Text().c_str() << std::endl;
}
}
IAsyncAction ProcessFeedAsync()
{
Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
SyndicationClient syndicationClient;
SyndicationFeed syndicationFeed{ co_await syndicationClient.RetrieveFeedAsync(rssFeedUri) };
PrintFeed(syndicationFeed);
}
int main()
{
winrt::init_apartment();
auto processOp{ ProcessFeedAsync() };
// do other work while the feed is being printed.
processOp.get(); // no more work to do; call get() so that we see the printout before the application exits.
}
Uma coroutina é uma função que pode ser suspensa e retomada. Na coroutina ProcessFeedAsync acima, quando a co_await instrução é alcançada, a coroutina inicia de forma assíncrona a chamada RetrieveFeedAsync e, em seguida, suspende-se imediatamente e retorna o controle de volta para o chamador (que é principal no exemplo acima).
main pode continuar a trabalhar enquanto o feed está sendo recuperado e impresso. Quando isso é feito (quando a chamada de RetrieveFeedAsync é concluída), a corrotina de ProcessFeedAsync é retomada na próxima instrução.
Você pode agregar uma corrotina em outras. Ou você pode chamar get para bloquear e aguardar a conclusão (e obter o resultado, se houver algum). Ou você pode passá-la para outra linguagem de programação que dê suporte à Windows Runtime.
Também é possível lidar com os eventos concluídos e/ou de progresso de ações e operações assíncronas usando delegados. Para obter detalhes e exemplos de código, consulte Tipos de delegado para ações e operações assíncronas.
Como você pode ver, no exemplo de código acima, continuamos usando a chamada de função get de bloqueio imediatamente antes do main de saída. Mas isso é apenas para que o aplicativo não encerre antes de terminar de imprimir a saída.
O modo assíncrono retorna um tipo do Windows Runtime
Neste próximo exemplo, encapsulamos uma chamada para RetrieveFeedAsync, para um URI específico, para nos dar uma função RetrieveBlogFeedAsync que retorna de forma assíncrona um SyndicationFeed.
// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
void PrintFeed(SyndicationFeed const& syndicationFeed)
{
for (SyndicationItem const& syndicationItem : syndicationFeed.Items())
{
std::wcout << syndicationItem.Title().Text().c_str() << std::endl;
}
}
IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> RetrieveBlogFeedAsync()
{
Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
SyndicationClient syndicationClient;
return syndicationClient.RetrieveFeedAsync(rssFeedUri);
}
int main()
{
winrt::init_apartment();
auto feedOp{ RetrieveBlogFeedAsync() };
// do other work.
PrintFeed(feedOp.get());
}
No exemplo acima, RetrieveBlogFeedAsync retorna um IAsyncOperationWithProgress, que tem um progresso e um valor retornado. Podemos fazer outro trabalho enquanto RetrieveBlogFeedAsync está fazendo sua coisa e recuperando o feed. Em seguida, chamamos get nesse objeto de operação assíncrona para bloquear, esperar que ela seja concluída e, em seguida, obter os resultados da operação.
Se você estiver retornando um tipo do Windows Runtime de forma assíncrona, deverá retornar um IAsyncOperation<TResult> ou um IAsyncOperationWithProgress<TResult, TProgress>. Qualquer classe de tempo de execução primária ou de terceiros está qualificada, ou qualquer tipo que possa ser passado de ou para uma função do Windows Runtime (por exemplo, int ou winrt::hstring). O compilador ajudará você indicando o erro "T precisa ser do tipo WinRT" se você tentar usar um desses tipos de operação assíncrona com um tipo que não seja do Windows Runtime.
Se uma coroutina não tiver pelo menos uma instrução co_await, então, para se qualificar como coroutina, ela deverá ter pelo menos uma instrução co_return ou uma instrução co_yield. Há casos em que a corrotina pode retornar um valor sem apresentar nenhuma assincronia e, portanto, sem bloquear nem alternar o contexto. Aqui está um exemplo que faz isso (as segundas e subsequentes vezes em que é chamado) armazenando um valor em cache.
winrt::hstring m_cache;
IAsyncOperation<winrt::hstring> ReadAsync()
{
if (m_cache.empty())
{
// Asynchronously download and cache the string.
}
co_return m_cache;
}
Retornar um tipo que não seja do Windows Runtime de forma assíncrona
Se você for retornar de forma assíncrona um tipo que não seja um tipo do Windows Runtime, deverá retornar um concurrency::task da Parallel Patterns Library (PPL). Recomendamos concurrency::task porque oferece melhor desempenho (e melhor compatibilidade no futuro) do que std::future.
Tip
Se você incluir <pplawait.h>, será possível usar concurrency::task como um tipo de corrotina.
// main.cpp
#include <iostream>
#include <ppltasks.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
concurrency::task<std::wstring> RetrieveFirstTitleAsync()
{
return concurrency::create_task([]
{
Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
SyndicationClient syndicationClient;
SyndicationFeed syndicationFeed{ syndicationClient.RetrieveFeedAsync(rssFeedUri).get() };
return std::wstring{ syndicationFeed.Items().GetAt(0).Title().Text() };
});
}
int main()
{
winrt::init_apartment();
auto firstTitleOp{ RetrieveFirstTitleAsync() };
// Do other work here.
std::wcout << firstTitleOp.get() << std::endl;
}
Passagem de parâmetros
Para funções síncronas, você deve usar const& parâmetros por padrão. Isso evitará a sobrecarga de cópias (que envolvem a contagem de referências e isso significa incrementos e decréscimos interligados).
// Synchronous function.
void DoWork(Param const& value);
Mas você pode ter problemas ao passar um parâmetro de referência para uma corrotina.
// NOT the recommended way to pass a value to a coroutine!
IASyncAction DoWorkAsync(Param const& value)
{
// While it's ok to access value here...
co_await DoOtherWorkAsync(); // (this is the first suspension point)...
// ...accessing value here carries no guarantees of safety.
}
Em uma corrotina, a execução é síncrona até o primeiro ponto de suspensão, no qual o controle é retornado para o autor da chamada e o quadro de chamada sai do escopo. Quando a corrotina é retomada, qualquer coisa pode ter acontecido com o valor de origem que faz referência a um parâmetro de referência. Da perspectiva da coroutina, um parâmetro de referência tem tempo de vida descontrolado. Portanto, no exemplo acima, podemos acessar com segurança value até co_await, mas não depois disso. Caso o valor seja destruído pelo chamador, tentar acessá-lo dentro da corrotina depois disso resultará em uma corrupção de memória. Também não podemos passar o valor com segurança para DoOtherWorkAsync se houver algum risco de que essa função, por sua vez, seja suspensa e tente usar o valor depois que ela for retomada.
Para tornar os parâmetros seguros para uso após a suspensão e retomada, as corrotinas devem usar passagem por valor por padrão a fim de assegurar que elas capturem pelo valor e evitar problemas de tempo de vida. Casos em que você pode se desviar dessa orientação porque tem certeza de que é seguro fazê-lo serão raros.
// Coroutine
IASyncAction DoWorkAsync(Param value); // not const&
A passagem por valor exige que a movimentação ou cópia do argumento tenha baixo custo; e esse normalmente é o caso de um ponteiro inteligente.
Também se pode argumentar que (a menos que você queira mover o valor) passar por valor constante é uma boa prática. Ele não terá nenhum efeito sobre o valor de origem do qual você está fazendo uma cópia, mas deixa a intenção clara e ajuda se você modificar inadvertidamente a cópia.
// coroutine with strictly unnecessary const (but arguably good practice).
IASyncAction DoWorkAsync(Param const value);
Consulte também Matrizes e vetores padrão, que aborda como passar um vetor padrão para um computador chamado assíncrono.
Se não puder alterar a assinatura de sua corrotina, mas puder alterar a implementação, você poderá fazer uma cópia local antes do primeiro co_await.
IASyncAction DoWorkAsync(Param const& value)
{
auto safe_value = value;
// It's ok to access both safe_value and value here.
co_await DoOtherWorkAsync();
// It's ok to access only safe_value here (not value).
}
Se for caro copiar Param, extraia apenas as partes necessárias antes do primeiro co_await.
IASyncAction DoWorkAsync(Param const& value)
{
auto safe_data = value.data;
// It's ok to access safe_data, value.data, and value here.
co_await DoOtherWorkAsync();
// It's ok to access only safe_data here (not value.data, nor value).
}
Acessar com segurança o ponteiro this em uma corrotina de membro de classe
Consulte referências fortes e fracas em C++/WinRT.
APIs importantes
- Classe concurrency::task
- Interface IAsyncAction
- interface IAsyncActionWithProgress<TProgress>
- IAsyncOperation<TResult> interface
- IAsyncOperationWithProgress<TResult, TProgress> interface
- Método SyndicationClient::RetrieveFeedAsync
- Classe SyndicationFeed
Tópicos relacionados:
Windows developer