방법: WRL을 사용하여 비동기 작업 완료

이 문서에서는 Windows 런타임 C++ WRL(템플릿 라이브러리)을 사용하여 비동기 작업을 시작하고 작업이 완료되면 작업을 수행하는 방법을 보여 줍니다.

이 문서에서는 두 가지 예를 보여 줍니다. 첫 번째 예제에서는 비동기 타이머를 시작하고 타이머가 만료되기를 기다립니다. 이 예제에서는 타이머 개체를 만들 때 비동기 작업을 지정합니다. 두 번째 예제에서는 백그라운드 작업자 스레드를 실행합니다. 이 예제에서는 인터페이스를 반환하는 Windows 런타임 메서드를 IAsyncInfo 사용하는 방법을 보여줍니다. 콜백 함수는 비동기 작업의 결과를 처리하는 이벤트 처리기를 지정할 수 있기 때문에 두 예제의 중요한 부분입니다.

구성 요소의 인스턴스를 만들고 속성 값을 검색하는 보다 기본적인 예제는 방법: Windows 런타임 구성 요소 활성화 및 사용을 참조하세요.

이러한 예제에서는 람다 식을 사용하여 콜백을 정의합니다. 함수 개체(functors), 함수 포인터 또는 std::function 개체를 사용할 수도 있습니다. C++ 람다 식에 대한 자세한 내용은 람다 식을 참조 하세요.

예: 타이머 작업

다음 단계에서는 비동기 타이머를 시작하고 타이머가 만료되기를 기다립니다. 전체 예제는 다음과 같습니다.

Warning

일반적으로 UWP(유니버설 Windows 플랫폼) 앱에서 Windows 런타임 C++ 템플릿 라이브러리를 사용하지만 이 예제에서는 그림에 콘솔 앱을 사용합니다. 이러한 wprintf_s 함수는 UWP 앱에서 사용할 수 없습니다. UWP 앱에서 사용할 수 있는 형식 및 함수에 대한 자세한 내용은 유니버설 Windows 플랫폼 앱에서 지원되지 않는 CRT 함수와 UWP 앱용 Win32 및 COM을 참조하세요.

  1. #include필요한 Windows 런타임, Windows 런타임 C++ 템플릿 라이브러리 또는 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 는 비동기 타이머를 사용하는 데 필요한 형식을 선언합니다.

    .cpp 파일의 using namespace 지시문을 활용하여 코드를 보다 읽기 쉽게 만드는 것이 좋습니다.

  2. Windows 런타임 초기화합니다.

    // Initialize the Windows Runtime.
    RoInitializeWrapper initialize(RO_INIT_MULTITHREADED);
    if (FAILED(initialize))
    {
        return PrintError(__LINE__, initialize);
    }
    
  3. 인터페이스에 대한 활성화 팩터리를 만듭니다 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 런타임 정규화된 이름을 사용하여 형식을 식별합니다. RuntimeClass_Windows_System_Threading_ThreadPoolTimer 매개 변수는 Windows 런타임 제공되고 필요한 런타임 클래스 이름을 포함하는 문자열입니다.

  4. 타이머 콜백을 기본 앱과 동기화하는 Event 개체를 만듭니다.

    // 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);
    }
    

    참고 항목

    이 이벤트는 콘솔 앱의 일부로만 데모용입니다. 이 예제에서는 이벤트를 사용하여 앱이 종료되기 전에 비동기 작업이 완료되도록 합니다. 대부분의 앱에서는 일반적으로 비동기 작업이 완료되기를 기다리지 않습니다.

  5. IThreadPoolTimer 2초 후에 만료되는 개체를 만듭니다. 함수를 Callback 사용하여 이벤트 처리기(개체)를 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. 콘솔에 메시지를 인쇄하고 타이머 콜백이 완료되기를 기다립니다. 모든 ComPtr 및 RAII 개체는 범위를 벗어나면 자동으로 릴리스됩니다.

    // 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.
    

전체 예제는 다음과 같습니다.

// 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.
*/

코드 컴파일

코드를 컴파일하려면 코드를 복사한 다음 Visual Studio 프로젝트에 붙여넣거나 이름이 지정된 wrl-consume-async.cpp 파일에 붙여넣은 다음 Visual Studio 명령 프롬프트 창에서 다음 명령을 실행합니다.

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

예: 백그라운드 스레드 작업

다음 단계에서는 작업자 스레드를 시작하고 해당 스레드에서 수행하는 작업을 정의합니다. 전체 예제는 다음과 같습니다.

이 예제에서는 인터페이스를 사용하는 방법을 보여 줍니다 ABI::Windows::Foundation::IAsyncAction . 다음을 구현하는 모든 인터페이스에 이 패턴을 적용할 수 있습니다.IAsyncInfoIAsyncActionIAsyncActionWithProgressIAsyncOperationIAsyncOperationWithProgress

  1. #include필요한 Windows 런타임, Windows 런타임 C++ 템플릿 라이브러리 또는 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는 작업자 스레드를 사용하는 데 필요한 형식을 선언합니다.

    코드를 더 읽기 쉽게 만들려면 .cpp 파일의 지시문을 사용하는 using namespace 것이 좋습니다.

  2. Windows 런타임 초기화합니다.

    // Initialize the Windows Runtime.
    RoInitializeWrapper initialize(RO_INIT_MULTITHREADED);
    if (FAILED(initialize))
    {
        return PrintError(__LINE__, initialize);
    }
    
  3. 인터페이스에 대한 활성화 팩터리를 만듭니다 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. 작업자 스레드 완료를 기본 앱과 동기화하는 Event 개체를 만듭니다.

    // 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);
    }
    

    참고 항목

    이 이벤트는 콘솔 앱의 일부로만 데모용입니다. 이 예제에서는 이벤트를 사용하여 앱이 종료되기 전에 비동기 작업이 완료되도록 합니다. 대부분의 앱에서는 일반적으로 비동기 작업이 완료되기를 기다리지 않습니다.

  5. 메서드를 IThreadPoolStatics::RunAsync 호출하여 작업자 스레드를 만듭니다. 함수를 Callback 사용하여 작업을 정의합니다.

    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);
    }
    

    함수 IsPrime 는 다음 전체 예제에서 정의됩니다.

  6. 콘솔에 메시지를 인쇄하고 스레드가 완료되기를 기다립니다. 모든 ComPtr 및 RAII 개체는 범위를 벗어나면 자동으로 릴리스됩니다.

    // 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.
    

전체 예제는 다음과 같습니다.

// 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.
*/

코드 컴파일

코드를 컴파일하려면 코드를 복사한 다음 Visual Studio 프로젝트에 붙여넣거나 이름이 지정된 wrl-consume-asyncOp.cpp 파일에 붙여넣은 다음 Visual Studio 명령 프롬프트 창에서 다음 명령을 실행합니다.

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

참고 항목

Windows 런타임 C++ 템플릿 라이브러리(WRL)