Operaciones simultáneas y asincrónicas con C++/WinRTConcurrency and asynchronous operations with C++/WinRT

Importante

En este tema se presentan los conceptos de corrutinas y co_await, que se recomiendan usar tanto en la UI como en las aplicaciones que no son de UI.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, la mayoría de los ejemplos de código de este tema introductorio muestran proyectos de la Aplicación de consola de Windows (C++/WinRT) .For simplicity, most of the code examples in this introductory topic show Windows Console Application (C++/WinRT) projects. En los ejemplos de código últimos de este tema se usan corrutinas, pero, para mayor comodidad, los ejemplos de aplicación de consola también siguen usando la llamada de función de bloqueo get justo antes de salir, de modo que la aplicación no sale antes de finalizar la impresión de la salida.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. Tú no hará eso (llamar a la función de bloqueo get) desde un subproceso de UI.You won't do that (call the blocking get function) from a UI thread. En su lugar, usarás la instrucción co_await.Instead, you'll use the co_await statement. Las técnicas que usarás en las aplicaciones de UI se describen en el tema Simultaneidad y asincronía más avanzadas.The techniques that you'll use in your UI applications are described in the topic More advanced concurrency and asynchrony.

En este tema introductorio se muestran algunas de las maneras en que puedes crear y consumir objetos asincrónicos de Windows Runtime con 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. Después de leer este tema, especialmente en el caso de las técnicas que vas a usar en las aplicaciones de UI, consulta también Simultaneidad y asincronía más avanzadas.After reading this topic, especially for techniques you'll use in your UI applications, also see More advanced concurrency and asynchrony.

Operaciones asincrónicas y funciones "Async" de Windows RuntimeAsynchronous operations and Windows Runtime "Async" functions

Cualquier API de Windows Runtime que tenga el potencial de tardar más de 50 milisegundos en completarse se implementa como una función asincrónica (con un nombre terminado en "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"). La implementación de una función asincrónica inicia el trabajo en otro subproceso, y se devuelve inmediatamente con un objeto que representa la operación asincrónica.The implementation of an asynchronous function initiates the work on another thread, and returns immediately with an object that represents the asynchronous operation. Cuando se completa la operación asincrónica, dicho objeto devuelto contiene cualquier valor que resulte del trabajo.When the asynchronous operation completes, that returned object contains any value that resulted from the work. El espacio de nombres de Windows Runtime Windows::Foundation contiene cuatro tipos de objeto de la operación asincrónica.The Windows::Foundation Windows Runtime namespace contains four types of asynchronous operation object.

Cada uno de estos tipos de la operación asincrónica se proyecta en un tipo correspondiente en el espacio de nombres winrt::Windows::Foundation de C++/WinRT.Each of these asynchronous operation types is projected into a corresponding type in the winrt::Windows::Foundation C++/WinRT namespace. C++/WinRT también contiene una estructura adaptadora await interna.C++/WinRT also contains an internal await adapter struct. No la usas directamente, pero gracias a dicha estructura puedes escribir una instrucción co_await para esperar de forma cooperativa el resultado de cualquier función que devuelva uno de estos tipos de la operación asincrónica.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. Y puedes crear tus propias corrutinas que devuelvan estos tipos.And you can author your own coroutines that return these types.

Un ejemplo de una función asincrónica de Windows es SyndicationClient::RetrieveFeedAsync, que devuelve un objeto de la operación asincrónica de 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>.

Echemos un vistazo a algunas maneras —primero de bloqueo y luego de no bloqueo— de utilizar C++/WinRT para llamar a una API como esta.Let's look at some ways—first blocking, and then non-blocking—of using C++/WinRT to call an API such as that. Solo para ilustrar las ideas básicas, usaremos un proyecto de Aplicación de consola de Windows (C++/WinRT) en los siguientes ejemplos 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. Las técnicas más adecuadas para una aplicación de UI se describen en Simultaneidad y asincronía más avanzadas.Techniques that are more appropriate for a UI application are discussed in More advanced concurrency and asynchrony.

Bloqueo del subproceso de llamadaBlock the calling thread

El siguiente ejemplo de código recibe un objeto de la operación asincrónica desde RetrieveFeedAsync y llama a get en dicho objeto para bloquear el subproceso de llamada hasta que los resultados de la operación asincrónica estén disponibles.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.

Si deseas copiar y pegar este ejemplo directamente en el archivo de código fuente principal de un proyecto de aplicación de consola Windows (C++/WinRT), establece primero No utilizar encabezados precompilados en las propiedades del proyecto.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();
}

Una llamada a get ofrece una codificación cómoda y es ideal para aplicaciones de consola o subprocesos en segundo plano donde no quieres usar una corrutina por el motivo que sea.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. Pero no es simultánea ni asincrónica, por lo que no es apropiada para un subproceso de interfaz de usuario (y se activará una aserción en compilaciones no optimizadas si intentas usarla en una).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 retener subprocesos del sistema operativo y que no puedan realizar otros trabajos útiles, necesitamos una técnica diferente.To avoid holding up OS threads from doing other useful work, we need a different technique.

Escritura de una corrutinaWrite a coroutine

C++/WinRT integra las corrutinas de C++ en el modelo de programación para proporcionar un modo natural de esperar un resultado de forma cooperativa.C++/WinRT integrates C++ coroutines into the programming model to provide a natural way to cooperatively wait for a result. Puedes producir tu propia operación asincrónica de Windows Runtime escribiendo un corrutina.You can produce your own Windows Runtime asynchronous operation by writing a coroutine. En el ejemplo de código siguiente, ProcessFeedAsync es la corrutina.In the code example below, ProcessFeedAsync is the coroutine.

Nota

La función get existe en el tipo de proyección de C++/WinRT winrt::Windows::Foundation::IAsyncAction, para que puedas llamar a la función desde cualquier proyecto de 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. No encontrarás la función enumerada como miembro de la interfaz IAsyncAction porque get no forma parte de la superficie de interfaz binaria de aplicaciones (ABI) del tipo de Windows Runtime real IAsyncAction.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.
}

Una corrutina es una función que se puede suspender y reanudar.A coroutine is a function that can be suspended and resumed. En la anterior corrutina ProcessFeedAsync, cuando se alcanza la instrucción co_await, la corrutina inicia de forma asincrónica la llamada RetrieveFeedAsync, después se suspende inmediatamente y devuelve el control al autor de la llamada (que es main en el ejemplo anterior).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 puede seguir funcionando mientras la fuente se recupera y se imprime.main can then continue to do work while the feed is being retrieved and printed. Una vez hecho esto (cuando se completa la llamada RetrieveFeedAsync), la corrutina ProcessFeedAsync se reanuda en la próxima instrucción.When that's done (when the RetrieveFeedAsync call completes), the ProcessFeedAsync coroutine resumes at the next statement.

Puedes agregar una corrutina en otras corrutinas.You can aggregate a coroutine into other coroutines. O puedes llamar a get para bloquear y esperar a que se complete (y obtener el resultado, si lo hay).Or you can call get to block and wait for it to complete (and get the result if there is one). O bien, puedes pasarla a otro lenguaje de programación compatible con Windows Runtime.Or you can pass it to another programming language that supports the Windows Runtime.

También es posible controlar los eventos de acciones y operaciones asincrónicas completados o en progreso mediante delegados.It's also possible to handle the completed and/or progress events of asynchronous actions and operations by using delegates. Para información más detallada y ejemplos de código, consulta Tipos de delegados para acciones y operaciones asincrónicas.For details, and code examples, see Delegate types for asynchronous actions and operations.

Como puedes ver, en el ejemplo de código anterior, seguiremos usando la llamada de función de bloqueo get justo antes de salir de main.As you can see, in the code example above, we continue to use the blocking get function call just before exiting main. Pero esto solo es para que la aplicación no salga antes de finalizar la impresión de la salida.But that's only so that the application doesn't exit before finishing printing its output.

Devolución de forma asincrónica de un tipo de Windows RuntimeAsynchronously return a Windows Runtime type

En el siguiente ejemplo, encapsulamos una llamada a RetrieveFeedAsync de un URI específico para que nos dé una función RetrieveBlogFeedAsync que devuelve asincrónicamente **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());
}

En el ejemplo anterior, RetrieveBlogFeedAsync devuelve IAsyncOperationWithProgress, que tiene progreso y un valor devuelto.In the example above, RetrieveBlogFeedAsync returns an IAsyncOperationWithProgress, which has both progress and a return value. Podemos hacer otro trabajo mientras RetrieveBlogFeedAsync está haciendo el suyo y recuperando la fuente.We can do other work while RetrieveBlogFeedAsync is doing its thing and retrieving the feed. A continuación, llamamos a get en este objeto de la operación asincrónica para bloquear, esperamos a que se complete y obtenemos los resultados de la operación.Then, we call get on that asynchronous operation object to block, wait for it to complete, and then obtain the results of the operation.

Si vas a devolver asincrónicamente un tipo de Windows Runtime, debes devolver IAsyncOperation<TResult> o IAsyncOperationWithProgress<TResult, TProgress>.If you're asynchronously returning a Windows Runtime type, then you should return an IAsyncOperation<TResult> or an IAsyncOperationWithProgress<TResult, TProgress>. Se cualifica cualquier clase de tiempo de ejecución propia o de terceros o cualquier tipo que se pueda pasar a una función de Windows Runtime o desde ella (por ejemplo, int o 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). El compilador le ayudará con un error "T debe ser de tipo WinRT" si intenta usar uno de estos tipos de operación asincrónica con un tipo que no sea de Windows Runtime.The compiler will help you with a "T must be WinRT type" error if you try to use one of these asynchronous operation types with a non-Windows Runtime type.

Si una corrutina no tiene al menos una instrucción co_await, para que se cualifique como un corrutina, debe tener al menos una instrucción co_return o 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. Habrá casos en los que la corrutina pueda devolver un valor sin introducir ninguna asincronía y, por lo tanto, sin bloquear ni cambiar el contexto.There will be cases where your coroutine can return a value without introducing any asynchrony, and therefore without blocking nor switching context. Este ejemplo lo hace (la segunda y subsiguientes veces que se realice la llamada) almacenando en caché un valor.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;
}

Devolución de forma asincrónica de un tipo que no sea de Windows RuntimeAsynchronously return a non-Windows-Runtime type

Si vas a devolver de forma asincrónica un tipo que no sea de Windows Runtime, debes devolver una biblioteca de patrones de procesamiento paralelo (PPL) 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. Te recomendamos utilizar concurrency::task porque te proporcionará un mejor rendimiento (y una mejor compatibilidad en el futuro) que std::future.We recommend concurrency::task because it gives you better performance (and better compatibility going forward) than std::future does.

Sugerencia

Si incluyes <pplawait.h>, puedes usar concurrency::task como un tipo de corrutina.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;
}

Paso de parámetrosParameter-passing

Para las funciones sincrónicas, debes usar los parámetros const& de manera predeterminada.For synchronous functions, you should use const& parameters by default. Eso evitará la sobrecarga de copias (que implican recuento de referencias, lo que significa incrementos y decrementos entrelazados).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);

Pero puedes experimentar problemas si pasas un parámetro de referencia a una corrutina.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.
}

En una corrutina, la ejecución es sincrónica hasta el primer punto de suspensión, donde el control se devuelve al autor de la llamada, y el marco de la llamada sale del ámbito.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. Cuando se reanuda la corrutina, puede haber ocurrido cualquier cosa en el valor de origen que hace referencia a un parámetro de referencia.By the time the coroutine resumes, anything might have happened to the source value that a reference parameter references. Desde la perspectiva de la corrutina, un parámetro de referencia tiene un ciclo de vida incontrolado.From the coroutine's perspective, a reference parameter has uncontrolled lifetime. Por tanto, en el ejemplo anterior, estamos seguros al acceder a value hasta co_await, pero no tras él.So, in the example above, we're safe to access value up until the co_await, but not after it. En caso de que el autor de llamada destruya value, intenta acceder a él dentro de la corrutina después de que eso provoque un daño de memoria.In the event that value is destructed by the caller, attempting to access it inside the coroutine after that results in a memory corruption. Tampoco podemos pasar con seguridad value a DoOtherWorkAsync si hay algún riesgo de que esa función se suspenda e intente usar value después de que se reanude.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 que los parámetros sean seguros de usar tras suspenderse y reanudarse, las corrutinas deben usar paso-por-valor de manera predeterminada para garantizar que capturen por valor y eviten los problemas de ciclo 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. Serán excepcionales los casos en los que puedas desviarte de esa orientación porque estés convencido de que sea seguro hacerlo.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&

Pasar por valor requiere que el argumento resulte económico de mover o copiar; y normalmente es el caso de un puntero inteligente.Passing by value requires that the argument be inexpensive to move or copy; and that's typically the case for a smart pointer.

También es discutible que (a menos que quieras mover el valor) pasar objetos por valor const sea una buena práctica.It's also arguable that (unless you want to move the value) passing by const value is good practice. No tendrá ningún efecto en el valor de origen desde el que estás haciendo una copia, pero hace que el propósito sea claro y sirve de ayuda si modificas accidentalmente la copia.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);

Consulta también Matrices y vectores estándares, que trata de cómo pasar un vector estándar a un destinatario asincrónico.Also see Standard arrays and vectors, which deals with how to pass a standard vector into an asynchronous callee.

Si no puedes cambiar la firma de la corrutina, pero puedes cambiar la implementación, podrás realizar una copia local antes de la primera instrucción 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).
}

Si el valor Param es difícil de copiar, extrae solo los elementos necesarios antes de la primera instrucción 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).
}

Acceso de forma segura al puntero this en una corrutina de miembro de claseSafely accessing the this pointer in a class-member coroutine

Consulta el artículo Referencias fuertes y débiles de C++/WinRT.See Strong and weak references in C++/WinRT.

API importantesImportant APIs