Simultaneidade e operações assíncronas com C++/WinRTConcurrency and asynchronous operations with C++/WinRT

Importante

Este tópico apresenta os conceitos de corrotinas e co_await, que recomendamos que você use na interface do usuário e em seus aplicativos que não sejam da interface do usuário.This topic introduces the concepts of coroutines and co_await, which we recommend that you use in both your UI and in your non-UI applications. Para simplificar, a maioria dos exemplos de código neste tópico introdutório mostra projetos Aplicativo de Console do Windows (C++/WinRT) .For simplicity, most of the code examples in this introductory topic show Windows Console Application (C++/WinRT) projects. 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.The later code examples in this topic do use coroutines, but for convenience the console application examples also continue to use the blocking get function call just before exiting, so that the application doesn't exit before finishing printing its output. Você não fará isso (chame a função get de bloqueio) em um thread da IU.You won't do that (call the blocking get function) from a UI thread. Em vez disso, você usará a instrução co_await.Instead, you'll use the co_await statement. As técnicas que você usará em seus aplicativos de interface do usuário são descritas no tópico Simultaneidade e assincronia mais avançadas.The techniques that you'll use in your UI applications are described in the topic More advanced concurrency and asynchrony.

Este tópico introdutório mostra algumas das maneiras como você pode criar e consumir objetos assíncronos do Windows Runtime com C++/WinRT.This introductory topic shows some of the ways in which you can both create and consume Windows Runtime asynchronous objects with C++/WinRT. Depois de ler este tópico, especialmente para as técnicas que você usará em seus aplicativos de interface do usuário, confira também Simultaneidade e assincronia mais avançadas.After reading this topic, especially for techniques you'll use in your UI applications, also see More advanced concurrency and asynchrony.

Operações assíncronas e funções "Async" do Windows RuntimeAsynchronous operations and Windows Runtime "Async" functions

Qualquer API do Windows Runtime que tem o potencial de demorar mais de 50 milissegundos para concluir é implementada como uma função assíncrona (com um nome terminado em "Async").Any Windows Runtime API that has the potential to take more than 50 milliseconds to complete is implemented as an asynchronous function (with a name ending in "Async"). A implementação de uma função assíncrona inicia o trabalho em outro thread e é retornada imediatamente com um objeto que representa a operação assíncrona.The implementation of an asynchronous function initiates the work on another thread, and returns immediately with an object that represents the asynchronous operation. Quando a operação assíncrona for concluída, aquele objeto retornado contém qualquer valor que resultou do trabalho.When the asynchronous operation completes, that returned object contains any value that resulted from the work. O namespace do Windows Runtime Windows::Foundation contém quatro tipos de objeto de operação assíncrona.The Windows::Foundation Windows Runtime namespace contains four types of asynchronous operation object.

Cada um desses tipos de operação assíncrona é projetado para um tipo correspondente no namespace C++/WinRT do winrt::Windows::Foundation.Each of these asynchronous operation types is projected into a corresponding type in the winrt::Windows::Foundation C++/WinRT namespace. C++/WinRT também contém um struct de adaptador de espera interno.C++/WinRT also contains an internal await adapter struct. Você não o usa diretamente, mas, graças a esse struct, é possível escrever uma instrução co_await para aguardar de maneira cooperativa o resultado de qualquer função que retorne um desses tipos de operação assíncrona.You don't use it directly but, thanks to that struct, you can write a co_await statement to cooperatively await the result of any function that returns one of these asynchronous operation types. Você pode criar suas próprias rotinas concomitantes que retornam esses tipos.And you can author your own coroutines that return these types.

Um exemplo de uma função do Windows assíncrona é SyndicationClient::RetrieveFeedAsync, que retorna um objeto de operação assíncrono do tipo IAsyncOperationWithProgress<TResult, TProgress> .An example of an asynchronous Windows function is SyndicationClient::RetrieveFeedAsync, which returns an asynchronous operation object of type IAsyncOperationWithProgress<TResult, TProgress>.

Vejamos algumas maneiras de—primeiro bloquear, depois desbloquear—usando o C++/WinRT para chamar uma API assim.Let's look at some ways—first blocking, and then non-blocking—of using C++/WinRT to call an API such as that. Apenas para ilustrar as ideias básicas, usaremos um projeto de Aplicativo de Console do Windows (C++/WinRT) nos próximos exemplos de código.Just for illustration of the basic ideas, we'll be using a Windows Console Application (C++/WinRT) project in the next few code examples. As técnicas mais apropriadas para um aplicativo de interface do usuário são discutidas Simultaneidade e assincronia mais avançadas.Techniques that are more appropriate for a UI application are discussed in More advanced concurrency and asynchrony.

Bloquear a conversa de chamadaBlock the calling thread

O exemplo de código abaixo recebe um objeto de operação assíncrona do RetrieveFeedAsync e chama get nesse objeto para bloquear o thread de chamada até os resultados da operação assíncrona estarem disponíveis.The code example below receives an asynchronous operation object from RetrieveFeedAsync, and it calls get on that object to block the calling thread until the results of the asynchronous operation are available.

Se você quiser copiar e colar esse exemplo diretamente no arquivo de código-fonte principal de um projeto de Aplicativo de Console do Windows (C++/WinRT) , primeiro defina Não usando cabeçalhos pré-compilados nas Propriedades do projeto.If you want to copy-paste this example directly into the main source code file of a Windows Console Application (C++/WinRT) project, then first set Not Using Precompiled Headers in project properties.

// 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.Calling get makes for convenient coding, and it's ideal for console apps or background threads where you may not want to use a coroutine for whatever reason. Mas ele não é simultâneo nem assíncrono, portanto, não é adequado para um thread de interface do usuário (e uma declaração será acionada em builds não otimizados caso tente usá-lo em um build desse tipo).But it's not concurrent nor asynchronous, so it's not appropriate for a UI thread (and an assertion will fire in unoptimized builds if you attempt to use it on one). Para evitar que threads do sistema operacional sejam impedidos de realizar outros trabalhos úteis, precisamos de uma técnica diferente.To avoid holding up OS threads from doing other useful work, we need a different technique.

Escrever uma corrotinaWrite a coroutine

C++/WinRT integra corrotinas de C++ no modelo de programação para fornecer uma maneira natural de esperar cooperativamente por um resultado.C++/WinRT integrates C++ coroutines into the programming model to provide a natural way to cooperatively wait for a result. Você pode produzir sua própria operação assíncrona do Windows Runtime criando uma corrotina.You can produce your own Windows Runtime asynchronous operation by writing a coroutine. No exemplo de código abaixo, ProcessFeedAsync é a corrotina.In the code example below, ProcessFeedAsync is the coroutine.

Observação

A função get existe no tipo de projeção C++/WinRT winrt::Windows::Foundation::IAsyncAction, portanto, você pode chamar a função de dentro de um projeto C++/WinRT.The get function exists on the C++/WinRT projection type winrt::Windows::Foundation::IAsyncAction, so you can call the function from within any C++/WinRT project. Você não encontrará a função listada como um membro da interface IAsyncAction, pois get não faz parte da superfície da ABI (interface binária do aplicativo) do tipo de Windows Runtime IAsyncAction propriamente dito.You will not find the function listed as a member of the IAsyncAction interface, because get is not part of the application binary interface (ABI) surface of the actual Windows Runtime type 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.
}

Um corrotina é uma função que pode ser suspensa e retomada.A coroutine is a function that can be suspended and resumed. Na corrotina ProcessFeedAsync acima, quando a instrução co_await é alcançada, a corrotina inicia assincronamente a chamada de RetrieveFeedAsync e, em seguida, ela imediatamente se suspende e retorna o controle de volta ao autor da chamada (que é main no exemplo acima).In the ProcessFeedAsync coroutine above, when the co_await statement is reached, the coroutine asynchronously initiates the RetrieveFeedAsync call and then it immediately suspends itself and returns control back to the caller (which is main in the example above). main pode então continuar a trabalhar enquanto o feed está sendo recuperado e impresso.main can then continue to do work while the feed is being retrieved and printed. Quando isso é feito (quando a chamada de RetrieveFeedAsync é concluída), a corrotina de ProcessFeedAsync é retomada na próxima instrução.When that's done (when the RetrieveFeedAsync call completes), the ProcessFeedAsync coroutine resumes at the next statement.

Você pode agregar uma corrotina em outras.You can aggregate a coroutine into other coroutines. Ou você pode chamar get para bloquear e aguardar a conclusão (e obter o resultado, se houver algum).Or you can call get to block and wait for it to complete (and get the result if there is one). Ou você pode passá-la para outra linguagem de programação compatível com o Windows Runtime.Or you can pass it to another programming language that supports the Windows Runtime.

Pelo uso de delegados, também é possível manipular os eventos de progresso e/ou concluídos de ações assíncronas e operações.It's also possible to handle the completed and/or progress events of asynchronous actions and operations by using delegates. Para obter detalhes e exemplos de código, consulte Tipos de delegado para ações assíncronas e operações.For details, and code examples, see Delegate types for asynchronous actions and operations.

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.As you can see, in the code example above, we continue to use the blocking get function call just before exiting main. Mas isso é apenas para que o aplicativo não saia antes de concluir a impressão da saída.But that's only so that the application doesn't exit before finishing printing its output.

O modo assíncrono retorna um tipo do Windows RuntimeAsynchronously return a Windows Runtime type

Neste exemplo, nós 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.In this next example we wrap a call to RetrieveFeedAsync, for a specific URI, to give us a RetrieveBlogFeedAsync function that asynchronously returns a 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 progresso e valor retornado.In the example above, RetrieveBlogFeedAsync returns an IAsyncOperationWithProgress, which has both progress and a return value. Podemos realizar outros trabalhos enquanto RetrieveBlogFeedAsync está fazendo sua parte e recuperando o feed.We can do other work while RetrieveBlogFeedAsync is doing its thing and retrieving the feed. Em seguida, chamamos get nesse objeto de operação assíncrona para bloquear, aguardamos sua conclusão e, em seguida, obtemos os resultados da operação.Then, we call get on that asynchronous operation object to block, wait for it to complete, and then obtain the results of the operation.

Se estiver retornando assincronamente um tipo Windows Runtime, será possível retornar um IAsyncOperation<TResult> ou IAsyncOperationWithProgress<TResult, TProgress> .If you're asynchronously returning a Windows Runtime type, then you should return an IAsyncOperation<TResult> or an 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).Any first- or third-party runtime class qualifies, or any type that can be passed to or from a Windows Runtime function (for example, int, or winrt::hstring). O compilador ajudará com um erro "precisa ser tipo WinRT" se você tentar usar um desses tipos de operação assíncrona com um tipo que não seja do Windows Runtime.The compiler will help you with a "must be WinRT type" error if you try to use one of these asynchronous operation types with a non-Windows Runtime type.

Se uma corrotina não tiver então pelo menos uma declaração co_await, para se qualificar como corrotina, ela precisará ter pelo menos uma declaração co_return ou co_yield.If a coroutine doesn't have at least one co_await statement then, in order to qualify as a coroutine, it must have at least one co_return or one co_yield statement. Há casos em que a corrotina pode retornar um valor sem apresentar nenhuma assincronia e, portanto, sem bloquear nem alternar o contexto.There will be cases where your coroutine can return a value without introducing any asynchrony, and therefore without blocking nor switching context. Aqui está um exemplo que faz isso (a segunda vez e as vezes subsequentes em que é chamado) armazenando um valor em cache.Here's an example that does that (the second and subsequent times it's called) by caching a value.

winrt::hstring m_cache;

IAsyncOperation<winrt::hstring> ReadAsync()
{
    if (m_cache.empty())
    {
        // Asynchronously download and cache the string.
    }
    co_return m_cache;
}

O modo assíncrono não retorna um tipo do Windows RuntimeAsynchronously return a non-Windows-Runtime type

Se estiver retornando assincronamente um tipo que não é um tipo do Windows Runtime, você deverá retornar uma PPL (biblioteca de padrões paralelos) concurrency::task.If you're asynchronously returning a type that's not a Windows Runtime type, then you should return a Parallel Patterns Library (PPL) concurrency::task. Recomendamos concurrency::task porque ela oferece melhor desempenho (e melhor compatibilidade no futuro) que std::future.We recommend concurrency::task because it gives you better performance (and better compatibility going forward) than std::future does.

Dica

Se você incluir <pplawait.h>, será possível usar concurrency::task como um tipo de corrotina.If you include <pplawait.h>, then you can use concurrency::task as a coroutine type.

// 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âmetroParameter-passing

Para funções síncronas, você deve usar os parâmetros const& por padrão.For synchronous functions, you should use const& parameters by default. Isso evitará a sobrecarga de cópias (o que envolve a contagem de referências, que por sua vez, significa aumentos e reduções interconectados).That will avoid the overhead of copies (which involve reference counting, and that means interlocked increments and decrements).

// Synchronous function.
void DoWork(Param const& value);

Mas você pode ter problemas ao passar um parâmetro de referência para uma corrotina.But you can run into problems if you pass a reference parameter to a coroutine.

// 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.In a coroutine, execution is synchronous up until the first suspension point, where control is returned to the caller and the calling frame goes out of scope. 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.By the time the coroutine resumes, anything might have happened to the source value that a reference parameter references. Da perspectiva da corrotina, um parâmetro de referência tem um tempo de vida não controlado.From the coroutine's perspective, a reference parameter has uncontrolled lifetime. Por isso, no exemplo acima, podemos acessar o valor até o co_await, mas não depois.So, in the example above, we're safe to access value up until the co_await, but not after it. Caso o valor seja destruído pelo chamador, tentar acessá-lo dentro da corrotina depois disso resultará em uma corrupção de memória.In the event that value is destructed by the caller, attempting to access it inside the coroutine after that results in a memory corruption. Nem poderemos passar com segurança o valor para DoOtherWorkAsync se houver qualquer risco de que a função seja suspensa e, em seguida, tente usar o valor após ser retomada.Nor can we safely pass value to DoOtherWorkAsync if there's any risk that that function will in turn suspend and then try to use value after it resumes.

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.To make parameters safe to use after suspending and resuming, your coroutines should use pass-by-value by default to ensure that they capture by value, and avoid lifetime issues. São raros os casos em que é possível ignorar essa diretriz tendo certeza de que é seguro fazê-lo.Cases when you can deviate from that guidance because you're certain that it's safe to do so are going to be rare.

// 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.Passing by value requires that the argument be inexpensive to move or copy; and that's typically the case for a smart pointer.

Também é possível afirmar que (exceto quando você quiser mover o valor) passar pelo valor const é uma boa prática.It's also arguable that (unless you want to move the value) passing by const value is good practice. Isso não afeta o valor de origem do qual você está fazendo uma cópia, mas torna a intenção clara e ajuda caso você modifique a cópia inadvertidamente.It won't have any effect on the source value from which you're making a copy, but it makes the intent clear, and helps if you inadvertently modify the copy.

// 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.Also see Standard arrays and vectors, which deals with how to pass a standard vector into an asynchronous callee.

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.If you can't change your coroutine's signature, but you can change the implementation, then you can make a local copy before the first 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, basta extrair apenas as partes necessárias antes do primeiro co_await.If Param is expensive to copy, then extract just the pieces you need before the first 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 classeSafely accessing the this pointer in a class-member coroutine

Confira Referências fortes e fracas em C++/WinRT.See Strong and weak references in C++/WinRT.

APIs importantesImportant APIs