Compartir a través de


Cómo: Completar operaciones asincrónicas mediante WRL

Este documento muestra cómo utilizar la biblioteca de plantillas de Windows Runtime C++ (WRL) para iniciar operaciones asincrónicas y para realizar el trabajo cuando las operaciones se completan.

En este documento se muestran dos ejemplos. El primer ejemplo inicia un temporizador asincrónico y espera a que expire el temporizador. En este ejemplo, se especifica la acción asincrónica al crear el objeto de temporizador. En el segundo ejemplo se ejecuta un subproceso de trabajo en segundo plano. En este ejemplo se muestra cómo trabajar con un método Windows Runtime que devuelve una interfaz IAsyncInfo. La función Callback es una parte importante de ambos ejemplos porque les permite especificar un controlador de eventos para procesar los resultados de las operaciones asincrónicas.

Para obtener un ejemplo más básico que crea una instancia de un componente y recupera un valor de propiedad, vea Cómo: Activar y usar un componente de Windows Runtime.

Sugerencia

En estos ejemplos se usan expresiones lambda para definir las devoluciones de llamada. También puede usar objetos de función (functors), punteros de función u objetos std::function . Para más información sobre las expresiones lambda, consulte Expresiones lambda.

Ejemplo: Trabajo con un temporizador

Los pasos siguientes inician un temporizador asincrónico y esperan a que expire el temporizador. A continuación se muestra el ejemplo completo.

Advertencia

Aunque normalmente se utiliza la biblioteca de plantillas de Windows Runtime C++ en una aplicación de la Plataforma universal de Windows (UWP), este ejemplo utiliza una aplicación de consola a modo de ilustración. Funciones como wprintf_s no están disponibles en una aplicación para UWP. Para más información sobre los tipos y funciones que puede utilizar en una aplicación para UWP, consulte Funciones de CRT no admitidas en aplicaciones de la Plataforma universal de Windows y Win32 y COM para aplicaciones para UWP.

  1. Incluya (#include) cualquier encabezado requerido de Windows Runtime, la biblioteca de plantillas de Windows Runtime C++ o la Biblioteca estándar de C++.

    #include <Windows.Foundation.h>
    #include <Windows.System.Threading.h>
    #include <wrl/event.h>
    #include <stdio.h>
    #include <Objbase.h>
    
    using namespace ABI::Windows::Foundation;
    using namespace ABI::Windows::System::Threading;
    using namespace Microsoft::WRL;
    using namespace Microsoft::WRL::Wrappers;
    

    Windows.System.Threading.h declara los tipos necesarios para usar un temporizador asincrónico.

    Se recomienda que use la directiva using namespace en el archivo .cpp para que el código sea más legible.

  2. Inicialice Windows Runtime.

    // Initialize the Windows Runtime.
    RoInitializeWrapper initialize(RO_INIT_MULTITHREADED);
    if (FAILED(initialize))
    {
        return PrintError(__LINE__, initialize);
    }
    
  3. Cree un generador de activación para la interfaz ABI::Windows::System::Threading::IThreadPoolTimer.

    // Get the activation factory for the IThreadPoolTimer interface.
    ComPtr<IThreadPoolTimerStatics> timerFactory;
    HRESULT hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_System_Threading_ThreadPoolTimer).Get(), &timerFactory);
    if (FAILED(hr))
    {
        return PrintError(__LINE__, hr);
    }
    

    Windows Runtime usa nombres completos para identificar tipos. El parámetro RuntimeClass_Windows_System_Threading_ThreadPoolTimer es una cadena proporcionada por Windows Runtime y contiene el nombre de clase del entorno de ejecución necesario.

  4. Cree un objeto Event que sincronice la devolución de llamada del temporizador a la aplicación principal.

    // Create an event that is set after the timer callback completes. We later use this event to wait for the timer to complete. 
    // This event is for demonstration only in a console app. In most apps, you typically don't wait for async operations to complete.
    Event timerCompleted(CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, WRITE_OWNER | EVENT_ALL_ACCESS));
    hr = timerCompleted.IsValid() ? S_OK : HRESULT_FROM_WIN32(GetLastError());
    if (FAILED(hr))
    {
        return PrintError(__LINE__, hr);
    }
    

    Nota:

    Este evento es para demostración solo como parte de una aplicación de consola. En este ejemplo se usa el evento para asegurarse de que se completa una operación asincrónica antes de que se cierre la aplicación. En la mayoría de las aplicaciones, normalmente no espera a que se completen las operaciones asincrónicas.

  5. Cree un objeto IThreadPoolTimer que expire después de dos segundos. Use la función Callback para crear el controlador de eventos (un objeto ABI::Windows::System::Threading::ITimerElapsedHandler).

    // Create a timer that prints a message after 2 seconds.
    
    TimeSpan delay;
    delay.Duration = 20000000; // 2 seconds.
    
    auto callback = Callback<ITimerElapsedHandler>([&timerCompleted](IThreadPoolTimer* timer) -> HRESULT
    {
        wprintf_s(L"Timer fired.\n");
    
        TimeSpan delay;
        HRESULT hr = timer->get_Delay(&delay);
        if (SUCCEEDED(hr))
        {
            wprintf_s(L"Timer duration: %2.2f seconds.\n", delay.Duration / 10000000.0);
        }
    
        // Set the completion event and return.
        SetEvent(timerCompleted.Get());
        return hr;
    });
    hr = callback ? S_OK : E_OUTOFMEMORY;
    if (FAILED(hr))
    {
        return PrintError(__LINE__, hr);
    }
    
    ComPtr<IThreadPoolTimer> timer;
    hr = timerFactory->CreateTimer(callback.Get(), delay, &timer);
    if (FAILED(hr))
    {
        return PrintError(__LINE__, hr);
    }
    
  6. Imprima un mensaje en la consola y espere a que se complete la devolución de llamada del temporizador. Todos los objetos ComPtr y RAII salen del ámbito y se liberan automáticamente.

    // Print a message and wait for the timer callback to complete.
    wprintf_s(L"Timer started.\nWaiting for timer...\n");
    
    // Wait for the timer to complete.
    WaitForSingleObjectEx(timerCompleted.Get(), INFINITE, FALSE);
    // All smart pointers and RAII objects go out of scope here.
    

A continuación se incluye el ejemplo completo:

// wrl-consume-async.cpp
// compile with: runtimeobject.lib
#include <Windows.Foundation.h>
#include <Windows.System.Threading.h>
#include <wrl/event.h>
#include <stdio.h>
#include <Objbase.h>

using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::System::Threading;
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;

// Prints an error string for the provided source code line and HRESULT
// value and returns the HRESULT value as an int.
int PrintError(unsigned int line, HRESULT hr)
{
    wprintf_s(L"ERROR: Line:%d HRESULT: 0x%X\n", line, hr);
    return hr;
}

int wmain()
{
    // Initialize the Windows Runtime.
    RoInitializeWrapper initialize(RO_INIT_MULTITHREADED);
    if (FAILED(initialize))
    {
        return PrintError(__LINE__, initialize);
    }

    // Get the activation factory for the IThreadPoolTimer interface.
    ComPtr<IThreadPoolTimerStatics> timerFactory;
    HRESULT hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_System_Threading_ThreadPoolTimer).Get(), &timerFactory);
    if (FAILED(hr))
    {
        return PrintError(__LINE__, hr);
    }

    // Create an event that is set after the timer callback completes. We later use this event to wait for the timer to complete. 
    // This event is for demonstration only in a console app. In most apps, you typically don't wait for async operations to complete.
    Event timerCompleted(CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, WRITE_OWNER | EVENT_ALL_ACCESS));
    hr = timerCompleted.IsValid() ? S_OK : HRESULT_FROM_WIN32(GetLastError());
    if (FAILED(hr))
    {
        return PrintError(__LINE__, hr);
    }

    // Create a timer that prints a message after 2 seconds.

    TimeSpan delay;
    delay.Duration = 20000000; // 2 seconds.

    auto callback = Callback<ITimerElapsedHandler>([&timerCompleted](IThreadPoolTimer* timer) -> HRESULT
    {
        wprintf_s(L"Timer fired.\n");

        TimeSpan delay;
        HRESULT hr = timer->get_Delay(&delay);
        if (SUCCEEDED(hr))
        {
            wprintf_s(L"Timer duration: %2.2f seconds.\n", delay.Duration / 10000000.0);
        }

        // Set the completion event and return.
        SetEvent(timerCompleted.Get());
        return hr;
    });
    hr = callback ? S_OK : E_OUTOFMEMORY;
    if (FAILED(hr))
    {
        return PrintError(__LINE__, hr);
    }

    ComPtr<IThreadPoolTimer> timer;
    hr = timerFactory->CreateTimer(callback.Get(), delay, &timer);
    if (FAILED(hr))
    {
        return PrintError(__LINE__, hr);
    }

    // Print a message and wait for the timer callback to complete.
    wprintf_s(L"Timer started.\nWaiting for timer...\n");

    // Wait for the timer to complete.
    WaitForSingleObjectEx(timerCompleted.Get(), INFINITE, FALSE);
    // All smart pointers and RAII objects go out of scope here.
}
/*
Output:
Timer started.
Waiting for timer...
Timer fired.
Timer duration: 2.00 seconds.
*/

Compilar el código

Para compilar el código, cópielo y péguelo en un proyecto de Visual Studio, o en un archivo denominado wrl-consume-async.cpp. Después, ejecute el comando siguiente en una ventana del símbolo del sistema de Visual Studio.

cl.exe wrl-consume-async.cpp runtimeobject.lib

Ejemplo: Trabajo con un subproceso en segundo plano

Los pasos siguientes inician un subproceso de trabajo y definen la acción que realiza ese subproceso. A continuación se muestra el ejemplo completo.

Sugerencia

En este ejemplo se muestra cómo trabajar con la interfaz ABI::Windows::Foundation::IAsyncAction. Puede aplicar este patrón a cualquier interfaz que implemente IAsyncInfo: IAsyncAction, IAsyncActionWithProgress, IAsyncOperation y IAsyncOperationWithProgress.

  1. Incluya (#include) cualquier encabezado requerido de Windows Runtime, la biblioteca de plantillas de Windows Runtime C++ o la Biblioteca estándar de C++.

    #include <Windows.Foundation.h>
    #include <Windows.System.Threading.h>
    #include <wrl/event.h>
    #include <stdio.h>
    #include <Objbase.h>
    
    using namespace ABI::Windows::Foundation;
    using namespace ABI::Windows::System::Threading;
    using namespace Microsoft::WRL;
    using namespace Microsoft::WRL::Wrappers;
    

    Windows.System.Threading.h declara los tipos necesarios para usar un subproceso de trabajo.

    Se recomienda que use la directiva using namespace en el archivo .cpp para que el código sea más legible.

  2. Inicialice Windows Runtime.

    // Initialize the Windows Runtime.
    RoInitializeWrapper initialize(RO_INIT_MULTITHREADED);
    if (FAILED(initialize))
    {
        return PrintError(__LINE__, initialize);
    }
    
  3. Cree un generador de activación para la interfaz ABI::Windows::System::Threading::IThreadPoolStatics.

    // Get the activation factory for the IThreadPoolStatics interface.
    ComPtr<IThreadPoolStatics> threadPool;
    HRESULT hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_System_Threading_ThreadPool).Get(), &threadPool);
    if (FAILED(hr))
    {
        return PrintError(__LINE__, hr);
    }
    
  4. Cree un objeto Event que sincronice la finalización del subproceso de trabajo con la aplicación principal.

    // Create an event that is set after the timer callback completes. We later use this event to wait for the timer to complete. 
    // This event is for demonstration only in a console app. In most apps, you typically don't wait for async operations to complete.
    Event threadCompleted(CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, WRITE_OWNER | EVENT_ALL_ACCESS));
    hr = threadCompleted.IsValid() ? S_OK : HRESULT_FROM_WIN32(GetLastError());
    if (FAILED(hr))
    {
        return PrintError(__LINE__, hr);
    }
    

    Nota:

    Este evento es para demostración solo como parte de una aplicación de consola. En este ejemplo se usa el evento para asegurarse de que se completa una operación asincrónica antes de que se cierre la aplicación. En la mayoría de las aplicaciones, normalmente no espera a que se completen las operaciones asincrónicas.

  5. Llame al método IThreadPoolStatics::RunAsync para crear un subproceso de trabajo. Use la función Callback para definir la acción.

    wprintf_s(L"Starting thread...\n");
    
    // Create a thread that computes prime numbers.
    ComPtr<IAsyncAction> asyncAction;
    hr = threadPool->RunAsync(Callback<IWorkItemHandler>([&threadCompleted](IAsyncAction* asyncAction) -> HRESULT
    {
        // Print a message.
        const unsigned int start = 0;
        const unsigned int end = 100000;
        unsigned int primeCount = 0;
        for (int n = start; n < end; n++)
        {
            if (IsPrime(n))
            {
                primeCount++;
            }
        }
    
        wprintf_s(L"There are %u prime numbers from %u to %u.\n", primeCount, start, end);
    
        // Set the completion event and return.
        SetEvent(threadCompleted.Get());
        return S_OK;
    
    }).Get(), &asyncAction);
    if (FAILED(hr))
    {
        return PrintError(__LINE__, hr);
    }
    

    La función IsPrime se define en el ejemplo completo siguiente.

  6. Imprima un mensaje en la consola y espere a que se complete el subproceso. Todos los objetos ComPtr y RAII salen del ámbito y se liberan automáticamente.

    // Print a message and wait for the thread to complete.
    wprintf_s(L"Waiting for thread...\n");
    
    // Wait for the thread to complete.
    WaitForSingleObjectEx(threadCompleted.Get(), INFINITE, FALSE);
    
    wprintf_s(L"Finished.\n");
    
    // All smart pointers and RAII objects go out of scope here.
    

A continuación se incluye el ejemplo completo:

// wrl-consume-asyncOp.cpp
// compile with: runtimeobject.lib 
#include <Windows.Foundation.h>
#include <Windows.System.Threading.h>
#include <wrl/event.h>
#include <stdio.h>
#include <Objbase.h>

using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::System::Threading;
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;

// Prints an error string for the provided source code line and HRESULT
// value and returns the HRESULT value as an int.
int PrintError(unsigned int line, HRESULT hr)
{
    wprintf_s(L"ERROR: Line:%d HRESULT: 0x%X\n", line, hr);
    return hr;
}

// Determines whether the input value is prime.
bool IsPrime(int n)
{
    if (n < 2)
    {
        return false;
    }
    for (int i = 2; i < n; ++i)
    {
        if ((n % i) == 0)
        {
            return false;
        }
    }
    return true;
}

int wmain()
{
    // Initialize the Windows Runtime.
    RoInitializeWrapper initialize(RO_INIT_MULTITHREADED);
    if (FAILED(initialize))
    {
        return PrintError(__LINE__, initialize);
    }

    // Get the activation factory for the IThreadPoolStatics interface.
    ComPtr<IThreadPoolStatics> threadPool;
    HRESULT hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_System_Threading_ThreadPool).Get(), &threadPool);
    if (FAILED(hr))
    {
        return PrintError(__LINE__, hr);
    }

    // Create an event that is set after the timer callback completes. We later use this event to wait for the timer to complete. 
    // This event is for demonstration only in a console app. In most apps, you typically don't wait for async operations to complete.
    Event threadCompleted(CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, WRITE_OWNER | EVENT_ALL_ACCESS));
    hr = threadCompleted.IsValid() ? S_OK : HRESULT_FROM_WIN32(GetLastError());
    if (FAILED(hr))
    {
        return PrintError(__LINE__, hr);
    }


    wprintf_s(L"Starting thread...\n");

    // Create a thread that computes prime numbers.
    ComPtr<IAsyncAction> asyncAction;
    hr = threadPool->RunAsync(Callback<IWorkItemHandler>([&threadCompleted](IAsyncAction* asyncAction) -> HRESULT
    {
        // Print a message.
        const unsigned int start = 0;
        const unsigned int end = 100000;
        unsigned int primeCount = 0;
        for (int n = start; n < end; n++)
        {
            if (IsPrime(n))
            {
                primeCount++;
            }
        }

        wprintf_s(L"There are %u prime numbers from %u to %u.\n", primeCount, start, end);

        // Set the completion event and return.
        SetEvent(threadCompleted.Get());
        return S_OK;

    }).Get(), &asyncAction);
    if (FAILED(hr))
    {
        return PrintError(__LINE__, hr);
    }

    // Print a message and wait for the thread to complete.
    wprintf_s(L"Waiting for thread...\n");

    // Wait for the thread to complete.
    WaitForSingleObjectEx(threadCompleted.Get(), INFINITE, FALSE);

    wprintf_s(L"Finished.\n");

    // All smart pointers and RAII objects go out of scope here.
}
/*
Output:
Starting thread...
Waiting for thread...
There are 9592 prime numbers from 0 to 100000.
Finished.
*/

Compilar el código

Para compilar el código, cópielo y péguelo en un proyecto de Visual Studio, o en un archivo denominado wrl-consume-asyncOp.cpp. A continuación, ejecute el comando siguiente en una ventana del símbolo del sistema de Visual Studio.

cl.exe wrl-consume-asyncOp.cpp runtimeobject.lib

Consulte también

Biblioteca de plantillas C++ de Windows en tiempo de ejecución (WRL)