Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Important
Este tópico introduz os conceitos de corrotinas e co_await, que recomendamos que utilize tanto na sua interface de utilizador como nas suas aplicações que não são de interface. Para simplificar, a maioria dos exemplos de código neste tópico introdutório mostra projetos de Aplicação de Consola Windows (C++/WinRT). Os exemplos de código apresentados mais à frente neste tópico usam corrotinas, mas, por conveniência, os exemplos de aplicação de consola também continuam a usar a chamada bloqueante da função get imediatamente antes de sair, para que a aplicação não termine antes de concluir a impressão da sua saída. Não vais fazer isso (chamar a função de bloqueio get ) a partir de um tópico de interface. Em vez disso, vais usar a co_await declaração. As técnicas que irá usar nas suas aplicações de interface de utilizador são descritas no tópico Concorrência avançada e assíncronia.
Este tópico introdutório mostra algumas das formas como pode criar e consumir objetos assíncronos do Windows Runtime com C++/WinRT. Depois de leres este tópico, sobretudo no que diz respeito às técnicas que utilizarás nas tuas aplicações de IU, consulta também Concorrência avançada e assincronia.
Operações assíncronas e funções "Async" do Windows Runtime
Qualquer API do Windows Runtime que tenha potencial para demorar mais de 50 milissegundos a ser concluída é implementada como uma função assíncrona (com um nome que termina em "Async"). A implementação de uma função assíncrona inicia o trabalho noutra thread e retorna imediatamente com um objeto que representa a operação assíncrona. Quando a operação assíncrona termina, esse objeto devolvido contém qualquer valor resultante do trabalho. O namespace Windows::Foundation do Windows Runtime contém quatro tipos de objetos de operação assíncrona.
- IAsyncAction,
- IAsyncActionWithProgress<TProgress>,
- IAsyncOperation<TResult>, e
- IAsyncOperationWithProgress<TResult, TProgress>.
Cada um destes tipos de operações assíncronas é mapeado para um tipo correspondente no namespace winrt::Windows::Foundation do C++/WinRT. C++/WinRT também contém uma estrutura interna de adaptador await. Não o usas diretamente, mas, graças a essa estrutura, podes escrever uma co_await instrução para aguardar cooperativamente o resultado de qualquer função que devolve um destes tipos de operações assíncronas. E pode criar as suas próprias corutinas que respondam a este tipo.
Um exemplo de função assíncrona do Windows é SyndicationClient::RetrieveFeedAsync, que devolve um objeto de operação assíncrona do tipo IAsyncOperationWithProgress<TResult, TProgress>.
Vamos analisar algumas formas — primeiro bloquear e depois não bloquear — de usar C++/WinRT para chamar uma API assim. Só para ilustrar as ideias básicas, vamos usar um projeto de Aplicação de Consola Windows (C++/WinRT) nos próximos exemplos de código. Técnicas mais adequadas para uma aplicação de interface de utilizador são discutidas em Concorrência avançada e assíncronia.
Bloquear o fio de chamada
O exemplo de código abaixo 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 quiseres copiar e colar este exemplo diretamente para o ficheiro principal de código-fonte de um projeto de Aplicação de Consola Windows (C++/WinRT), então primeiro define Não Usar 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 torna a programação mais prática e é ideal para aplicações de consola ou threads em segundo plano, em que poderá não querer usar uma corrotina, seja por que motivo for. Mas não é concorrente nem assíncrono, por isso não é apropriado para o thread da interface (e uma asserção será acionada em compilações não otimizadas se tentar usá-la nesse thread). Para evitar que os threads do sistema operativo atrasem outros trabalhos úteis, precisamos de uma técnica diferente.
Escreva uma corrotina
O C++/WinRT integra as corrotinas de C++ no modelo de programação para proporcionar uma forma natural de aguardar cooperativamente um resultado. Pode criar a sua própria operação assíncrona no Windows Runtime escrevendo 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, por isso podes chamar a função dentro de qualquer projeto C++/WinRT. Não encontrará a função indicada como membro da interface IAsyncAction, porque get não faz parte da superfície da ABI (interface binária de aplicação) do tipo real do Windows Runtime IAsyncAction.
// 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 corrotina é uma função que pode ser suspensa e retomada. Na corrotina ProcessFeedAsync acima, quando se chega à instrução co_await, a corrotina inicia de forma assíncrona a chamada RetrieveFeedAsync e, em seguida, suspende-se imediatamente e devolve o controlo ao chamador (que é main no exemplo acima). O main pode então continuar a executar outras tarefas enquanto o feed é obtido e impresso. Quando isso termina (quando a chamada RetrieveFeedAsync termina), a corrotina ProcessFeedAsync retoma na próxima instrução.
Pode agrupar uma corrotina noutras corrotinas. Ou podes chamar get para bloquear e esperar que conclua (e obter o resultado, se existir). Ou podes passá-lo para outra linguagem de programação que suporte o 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 detalhes e exemplos de código, veja Tipos de delegar para ações e operações assíncronas.
Como pode ver, no exemplo de código acima, continuamos a usar a chamada de função get bloqueante mesmo antes de sair de main. Mas isso serve apenas para que a aplicação não termine antes de acabar de imprimir a saída.
Devolver assíncronamente um tipo de Windows Runtime
Neste próximo exemplo, envolvemos uma chamada ao RetrieveFeedAsync, para um URI específico, para nos fornecer uma função RetrieveBlogFeedAsync que retorna assíncronamente 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, o RetrieveBlogFeedAsync devolve um IAsyncOperationWithProgress, que tem tanto progresso como valor de retorno. Podemos fazer outras tarefas enquanto o RetrieveBlogFeedAsync faz o seu trabalho e recupera o feed. Depois, chamamos get nesse objeto de operação assíncrona para bloquear, aguardar que seja concluída e, em seguida, obter os resultados da operação.
Se estiveres a devolver assíncronamente um tipo de Windows Runtime, então deves devolver um IAsyncOperation<TResult> ou um IAsyncOperationWithProgress<TResult, TProgress>. Qualquer classe de runtime própria ou de terceiros é elegível, bem como qualquer tipo que possa ser passado para ou devolvido por uma função do Windows Runtime (por exemplo, int, ou winrt::hstring). O compilador emitirá o erro "T deve ser do tipo WinRT" se tentar utilizar um destes tipos de operações assíncronas com um tipo que não seja do Windows Runtime.
Se uma corrotina não tiver pelo menos uma instrução co_await, então, para ser considerada uma corrotina, tem de ter pelo menos uma instrução co_return ou uma instrução co_yield. Haverá casos em que a sua corrotina pode devolver um valor sem introduzir qualquer assíncronia e, portanto, sem bloquear ou mudar de contexto. Aqui está um exemplo que faz isso (na segunda e nas vezes seguintes em que é chamado) ao armazenar 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;
}
Devolver assíncronamente um tipo que não seja do Windows Runtime
Se estiveres a devolver assíncronamente um tipo que não seja um tipo do Windows Runtime, deves devolver uma 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 incluir <pplawait.h>, então pode usar concurrency :task como 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, deves usar parâmetros const& por predefinição. Isso evita a sobrecarga das cópias (que envolvem contagem de referências, o que significa incrementos e decrementos entrelaçados).
// Synchronous function.
void DoWork(Param const& value);
Mas podes ter problemas se passares 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.
}
Numa corrotina, a execução é síncrona até ao primeiro ponto de suspensão, onde o controlo é devolvido ao chamador e o frame de chamada sai do âmbito. Quando a corrotina retoma, o valor de origem para o qual um parâmetro de referência aponta pode ter sofrido qualquer alteração. Do ponto de vista da corrotina, um parâmetro de referência tem uma vida útil descontrolada. Assim, no exemplo acima, é seguro aceder ao valor até ao co_await, mas não depois dele. Caso valor seja destruído pelo chamador, tentar aceder-lhe dentro da corrotina depois disso resulta em corrupção de memória. Também não podemos passar valor com segurança para DoOtherWorkAsync se existir o risco de essa função, por sua vez, suspender e depois tentar usar valor depois de ser retomada.
Para tornar os parâmetros seguros de utilizar após serem suspensos e retomados, as suas corrotinas devem usar passagem por valor por predefinição para garantir que capturam por valor e evitar problemas de tempo de vida. Casos em que consegue desviar-se dessa orientação porque tem a certeza de que é seguro fazê-lo serão raros.
// Coroutine
IASyncAction DoWorkAsync(Param value); // not const&
A passagem por valor exige que seja pouco dispendioso mover ou copiar o argumento; e esse é normalmente o caso de um ponteiro inteligente.
Também é discutível que (a menos que queiras mover o valor) passar pelo valor const é uma boa prática. Não terá qualquer efeito no valor fonte a partir do qual estás a fazer uma cópia, mas deixa clara a intenção e ajuda se modificares a cópia sem querer.
// coroutine with strictly unnecessary const (but arguably good practice).
IASyncAction DoWorkAsync(Param const value);
Veja também Arrays e vetores padrão, que trata de como passar um vetor padrão para um callee assíncrono.
Se não puder alterar a assinatura da sua corrotina, mas puder alterar a implementação, então pode criar uma cópia local antes da primeira 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 Param for caro de copiar, então extraia apenas as partes de que precisa 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).
}
Aceder em segurança ao ponteiro this numa corrotina de membro da classe
Veja Referências fortes e fracas em C++/WinRT.
APIs importantes
- concorrência::classe de tarefa
- Interface IAsyncAction
- Interface IAsyncActionWithProgress<TProgress>
- interface IAsyncOperation<TResult>
- interface IAsyncOperationWithProgress<TResult, TProgress>
- SyndicationClient::RetrieveFeedAsync método
- Classe SyndicationFeed
Tópicos relacionados
Windows developer