C++/WinRT를 통한 고급 동시성 및 비동기More advanced concurrency and asynchrony with C++/WinRT

이 항목에서는 C++/WinRT에서 동시성 및 비동기를 사용하는 고급 시나리오에 대해 설명합니다.This topic describes more advanced scenarios with concurrency and asynchrony in C++/WinRT.

이 주제에 대한 소개는 먼저 동시성 및 비동기 작업을 참조하세요.For an introduction to this subject, first read Concurrency and asynchronous operations.

Windows 스레드 풀에 작업 오프로딩Offloading work onto the Windows thread pool

코루틴은 함수가 실행을 반환할 때까지 호출자가 차단된다는 점에서 다른 함수와 같은 함수입니다.A coroutine is a function like any other in that a caller is blocked until a function returns execution to it. 또한 코루틴의 첫 번째 반환 기회는 첫 번째 co_await, co_return 또는 co_yield입니다.And, the first opportunity for a coroutine to return is the first co_await, co_return, or co_yield.

따라서 코루틴에서 컴퓨팅 바인딩된 작업을 수행하기 전에 호출자에 실행을 반환(즉, 일시 중단 지점 도입)하여 호출자가 차단되지 않도록 해야 합니다.So, before you do compute-bound work in a coroutine, you need to return execution to the caller (in other words, introduce a suspension point) so that the caller isn't blocked. 다른 일부 작업을 co_await하여 이미 수행하지 않은 경우, winrt::resume_background 함수를 co_await할 수 있습니다.If you're not already doing that by co_await-ing some other operation, then you can co_await the winrt::resume_background function. 그러면 컨트롤이 호출자에 반환되고, 스레드 풀 스레드에서 즉시 실행이 다시 시작됩니다.That returns control to the caller, and then immediately resumes execution on a thread pool thread.

구현에서 사용되는 스레드 풀은 하위 수준의 Windows 스레드 풀이므로 매우 효율적입니다.The thread pool being used in the implementation is the low-level Windows thread pool, so it's optimially efficient.

IAsyncOperation<uint32_t> DoWorkOnThreadPoolAsync()
{
    co_await winrt::resume_background(); // Return control; resume on thread pool.

    uint32_t result;
    for (uint32_t y = 0; y < height; ++y)
    for (uint32_t x = 0; x < width; ++x)
    {
        // Do compute-bound work here.
    }
    co_return result;
}

스레드 선호도를 고려한 프로그래밍Programming with thread affinity in mind

이 시나리오는 이전 시나리오를 확장합니다.This scenario expands on the previous one. 스레드 풀에 일부 작업을 오프로드한 후 UI(사용자 인터페이스)에 진행 상황을 표시하려고 합니다.You offload some work onto the thread pool, but then you want to display progress in the user interface (UI).

IAsyncAction DoWorkAsync(TextBlock textblock)
{
    co_await winrt::resume_background();
    // Do compute-bound work here.

    textblock.Text(L"Done!"); // Error: TextBlock has thread affinity.
}

TextBlock은 만든 스레드인 UI 스레드에서 업데이트해야 하므로, 위의 코드에서는 winrt::hresult_wrong_thread 예외가 throw됩니다.The code above throws a winrt::hresult_wrong_thread exception, because a TextBlock must be updated from the thread that created it, which is the UI thread. 한 가지 해결 방법은 코루틴이 원래 호출된 스레드 컨텍스트를 캡처하는 것입니다.One solution is to capture the thread context within which our coroutine was originally called. 이렇게 하려면 winrt::apartment_context 개체를 인스턴스화하고 백그라운드 작업을 수행한 다음, apartment_contextco_await하여 호출 컨텍스트로 다시 전환합니다.To do that, instantiate a winrt::apartment_context object, do background work, and then co_await the apartment_context to switch back to the calling context.

IAsyncAction DoWorkAsync(TextBlock textblock)
{
    winrt::apartment_context ui_thread; // Capture calling context.

    co_await winrt::resume_background();
    // Do compute-bound work here.

    co_await ui_thread; // Switch back to calling context.

    textblock.Text(L"Done!"); // Ok if we really were called from the UI thread.
}

TextBlock을 만든 UI 스레드에서 위의 코루틴을 호출하기만 하면 이 기술은 성공합니다.As long as the coroutine above is called from the UI thread that created the TextBlock, then this technique works. 앱에서 기술 성공을 확신할 수 있는 여러 가지 경우가 있습니다.There will be many cases in your app where you're certain of that.

호출 스레드가 확실하지 않은 경우에 적용되는, UI 업데이트에 대한 보다 일반적인 해결 방법을 위해 winrt::resume_foreground 함수를 co_await하여 특정 포그라운드 스레드로 전환할 수 있습니다.For a more general solution to updating UI, which covers cases where you're uncertain about the calling thread, you can co_await the winrt::resume_foreground function to switch to a specific foreground thread. 아래 코드 예제에서는 해당 Dispatcher 속성에 액세스하여 TextBlock과 연결된 디스패처 개체를 전달하여 포그라운드 스레드를 지정합니다.In the code example below, we specify the foreground thread by passing the dispatcher object associated with the TextBlock (by accessing its Dispatcher property). winrt::resume_foreground 구현이 해당 디스패처 개체에서 CoreDispatcher.RunAsync를 호출하여 코루틴에서 이후에 오는 작업을 실행합니다.The implementation of winrt::resume_foreground calls CoreDispatcher.RunAsync on that dispatcher object to execute the work that comes after it in the coroutine.

IAsyncAction DoWorkAsync(TextBlock textblock)
{
    co_await winrt::resume_background();
    // Do compute-bound work here.

    co_await winrt::resume_foreground(textblock.Dispatcher()); // Switch to the foreground thread associated with textblock.

    textblock.Text(L"Done!"); // Guaranteed to work.
}

코루틴의 실행 컨텍스트, 다시 시작 및 전환Execution contexts, resuming, and switching in a coroutine

대체로 코루틴의 일시 중단 지점 이후에는 원래의 실행 스레드가 사라지고, 임의의 스레드에서 다시 시작될 수 있습니다. 즉, 임의의 스레드에서 비동기 작업에 대해 Completed 메서드를 호출할 수 있습니다.Broadly speaking, after a suspension point in a coroutine, the original thread of execution may go away and resumption may occur on any thread (in other words, any thread may call the Completed method for the async operation).

그러나 네 가지 Windows 런타임 비동기 작업 유형(IAsyncXxx) 중 하나를 co_await하는 경우 C++/WinRT는 co_await한 지점에서 호출 컨텍스트를 캡처합니다.But if you co_await any of the four Windows Runtime asynchronous operation types (IAsyncXxx), then C++/WinRT captures the calling context at the point you co_await. 또한 연속이 다시 시작될 때 해당 컨텍스트에 있도록 합니다.And it ensures that you're still on that context when the continuation resumes. 이 작업을 위해 C++/WinRT는 호출 컨텍스트에 이미 있는지 여부를 확인하고, 호출 컨텍스트에 없을 경우 해당 컨텍스트로 전환합니다.C++/WinRT does this by checking whether you're already on the calling context and, if not, switching to it. co_await 이전에 STA(단일 스레드 아파트) 스레드에 있었다면 이후에도 동일한 스레드에 있습니다. co_await 이전에 MTA(다중 스레드 아파트) 스레드에 있었다면 이후에도 동일한 스레드에 있습니다.If you were on a single-threaded apartment (STA) thread before co_await, then you'll be on the same one afterward; if you were on a multi-threaded apartment (MTA) thread before co_await, then you'll be on one afterward.

IAsyncAction ProcessFeedAsync()
{
    Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
    SyndicationClient syndicationClient;

    // The thread context at this point is captured...
    SyndicationFeed syndicationFeed{ co_await syndicationClient.RetrieveFeedAsync(rssFeedUri) };
    // ...and is restored at this point.
}

이 동작에 의존할 수 있는 이유는 C++/WinRT에서 이러한 Windows 런타임 비동기 작업 유형을 C++ 코루틴 언어 지원에 맞게 조정하는 코드를 제공하기 때문입니다(이러한 코드 조각을 대기 어댑터라고 함).The reason you can rely on this behavior is because C++/WinRT provides code to adapt those Windows Runtime asynchronous operation types to the C++ coroutine language support (these pieces of code are called wait adapters). C++/WinRT에 남아 있는 대기 가능 유형은 단순히 스레드 풀 래퍼 및/또는 도우미이기 때문에 스레드 풀에서 완료됩니다.The remaining awaitable types in C++/WinRT are simply thread pool wrappers and/or helpers; so they complete on the thread pool.

using namespace std::chrono_literals;
IAsyncOperation<int> return_123_after_5s()
{
    // No matter what the thread context is at this point...
    co_await 5s;
    // ...we're on the thread pool at this point.
    co_return 123;
}

다른 일부 유형을 co_await하는 경우 C++/WinRT 코루틴 구현 내에서 수행하더라도 다른 라이브러리에서 어댑터를 제공하며, 다시 시작 및 컨텍스트 측면에서 해당 어댑터의 기능을 파악해야 합니다.If you co_await some other type—even within a C++/WinRT coroutine implementation—then another library provides the adapters, and you'll need to understand what those adapters do in terms of resumption and contexts.

컨텍스트 전환을 최소한으로 유지하려면, 이 항목에서 이미 확인한 몇 가지 기술을 사용할 수 있습니다.To keep context switches down to a minimum, you can use some of the techniques that we've already seen in this topic. 이 작업을 수행하는 몇 가지 예를 살펴보겠습니다.Let's see some illustrations of doing that. 다음 의사 코드 예제에서는 Windows 런타임 API를 호출하여 이미지를 로드하고, 백그라운드 스레드에 놓여 해당 이미지를 처리한 다음, UI 스레드로 돌아가 UI에 이미지를 표시하는 이벤트 처리기의 개요를 보여 줍니다.In this next pseudo-code example, we show the outline of an event handler that calls a Windows Runtime API to load an image, drops onto a background thread to process that image, and then returns to the UI thread to display the image in the UI.

IAsyncAction MainPage::ClickHandler(IInspectable /* sender */, RoutedEventArgs /* args */)
{
    // We begin in the UI context.

    // Call StorageFile::OpenAsync to load an image file.

    // The call to OpenAsync occurred on a background thread, but C++/WinRT has restored us to the UI thread by this point.

    co_await winrt::resume_background();

    // We're now on a background thread.

    // Process the image.

    co_await winrt::resume_foreground(this->Dispatcher());

    // We're back on MainPage's UI thread.

    // Display the image in the UI.
}

이 시나리오에서 StorageFile::OpenAsync 호출은 약간 비효율적입니다.For this scenario, there's a little bit of ineffiency around the call to StorageFile::OpenAsync. 처리기가 호출자에 실행을 반환할 수 있도록 다시 시작 시 필요한 백그라운드 스레드로의 컨텍스트 전환이 있으며, 다시 시작된 후 C++/WinRT가 UI 스레드 컨텍스트를 복원합니다.There's a necessary context switch to a background thread (so that the handler can return execution to the caller), on resumption after which C++/WinRT restores the UI thread context. 그러나 이 경우에는 UI를 업데이트할 때까지 UI 스레드에 있지 않아도 됩니다.But, in this case, it's not necessary to be on the UI thread until we're about to update the UI. winrt::resume_background 호출 ‘전에’ 호출하는 Windows 런타임 API가 많을수록, 불필요한 컨텍스트 전환이 많이 발생합니다. The more Windows Runtime APIs we call before our call to winrt::resume_background, the more unnecessary back-and-forth context switches we incur. 해결 방법은 그전에 Windows 런타임 API를 호출하지 ‘않는’ 것입니다. The solution is not to call any Windows Runtime APIs before then. 모든 호출을 winrt::resume_background 뒤로 이동합니다.Move them all after the winrt::resume_background.

IAsyncAction MainPage::ClickHandler(IInspectable /* sender */, RoutedEventArgs /* args */)
{
    // We begin in the UI context.

    co_await winrt::resume_background();

    // We're now on a background thread.

    // Call StorageFile::OpenAsync to load an image file.

    // Process the image.

    co_await winrt::resume_foreground(this->Dispatcher());

    // We're back on MainPage's UI thread.

    // Display the image in the UI.
}

보다 높은 수준의 작업을 수행하려는 경우 고유한 await 어댑터를 작성할 수 있습니다.If you want to do something more advanced, then you could write your own await adapters. 예를 들어 비동기 작업이 완료되는 스레드에서 co_await를 다시 시작하려는 경우(컨텍스트 전환 없음), 먼저 아래와 유사한 await 어댑터를 작성할 수 있습니다.For example, if you want a co_await to resume on the same thread that the async action completes on (so, there's no context switch), then you could begin by writing await adapters similar to the ones shown below.

참고

아래 코드 예제는 교육 목적으로만 제공되며, await 어댑터의 작동 방식 이해를 돕기 위한 것입니다.The code example below is provided for educational purposes only; it's to get you started understanding how await adapters work. 자체 코드베이스에서 이 기술을 사용하려는 경우 고유한 await 어댑터 구조체를 개발하고 테스트하는 것이 좋습니다.If you want to use this technique in your own codebase, then we recommend that you develop and test your own await adapter struct(s). 예를 들어 complete_on_any, complete_on_currentcomplete_on(dispatcher) 을 작성할 수 있습니다.For example, you could write complete_on_any, complete_on_current, and complete_on(dispatcher). 또한 IAsyncXxx 유형을 템플릿 매개 변수로 사용하는 템플릿으로 만드는 것이 좋습니다.Also consider making them templates that take the IAsyncXxx type as a template parameter.

struct no_switch
{
    no_switch(Windows::Foundation::IAsyncAction const& async) : m_async(async)
    {
    }

    bool await_ready() const
    {
        return m_async.Status() == Windows::Foundation::AsyncStatus::Completed;
    }

    void await_suspend(std::experimental::coroutine_handle<> handle) const
    {
        m_async.Completed([handle](Windows::Foundation::IAsyncAction const& /* asyncInfo */, Windows::Foundation::AsyncStatus const& /* asyncStatus */)
        {
            handle();
        });
    }

    auto await_resume() const
    {
        return m_async.GetResults();
    }

private:
    Windows::Foundation::IAsyncAction const& m_async;
};

no_switch await 어댑터를 사용하는 방법을 파악하려면 먼저 C++ 컴파일러에서 co_await 식을 발견할 경우 await_ready, await_suspend, await_resume 함수를 찾는다는 것을 알아야 합니다.To understand how to use the no_switch await adapters, you'll first need to know that when the C++ compiler encounters a co_await expression it looks for functions called await_ready, await_suspend, and await_resume. C++/WinRT 라이브러리는 기본적으로 다음과 같은 적절한 동작을 얻을 수 있도록 이러한 함수를 제공합니다.The C++/WinRT library provides those functions so that you get reasonable behavior by default, like this.

IAsyncAction async{ ProcessFeedAsync() };
co_await async;

no_switch await 어댑터를 사용하려면 다음과 같이 co_await 식의 유형을 IAsyncXxx에서 no_switch로 변경합니다.To use the no_switch await adapters, just change the type of that co_await expression from IAsyncXxx to no_switch, like this.

IAsyncAction async{ ProcessFeedAsync() };
co_await static_cast<no_switch>(async);

그러면 C++ 컴파일러에서 IAsyncXxx와 일치하는 await_xxx 함수 3개를 찾는 대신, no_switch와 일치하는 함수를 찾습니다.Then, instead of looking for the three await_xxx functions that match IAsyncXxx, the C++ compiler looks for functions that match no_switch.

winrt::resume_foreground에 대한 심층 분석A deeper dive into winrt::resume_foreground

C++/WinRT 2.0부터 winrt::resume_foreground 함수는 디스패처 스레드에서 호출되더라도 일시 중단됩니다(이전 버전에서는 아직 디스패처 스레드에 없는 경우에만 일시 중단되므로 일부 시나리오에서 교착 상태가 발생할 수 있음).As of C++/WinRT 2.0, the winrt::resume_foreground function suspends even if it's called from the dispatcher thread (in previous versions, it could introduce deadlocks in some scenarios because it only suspended if not already on the dispatcher thread).

현재 동작은 스택 해제 및 큐에 다시 넣기를 수행할 수 있음을 의미하며, 특히 낮은 시스템 코드 수준의 시스템 안정성에 매우 중요합니다.The current behavior means that you can rely on stack unwinding and re-queuing taking place; and that's important for system stability, especially in low-level systems code. 위의 스레드 선호도를 고려한 프로그래밍 섹션의 마지막 코드 목록은 백그라운드 스레드에서 복잡한 계산을 수행한 다음, UI(사용자 인터페이스)를 업데이트하기 위해 적절한 UI 스레드로 전환하는 것을 보여 줍니다.The last code listing in the section Programming with thread affinity in mind, above, illustrates performing some complex calculation on a background thread, and then switching to the appropriate UI thread in order to update the user interface (UI).

winrt::resume_foreground가 내부적으로 표시되는 방법은 다음과 같습니다.Here's how winrt::resume_foreground looks internally.

auto resume_foreground(...) noexcept
{
    struct awaitable
    {
        bool await_ready() const
        {
            return false; // Queue without waiting.
            // return m_dispatcher.HasThreadAccess(); // The C++/WinRT 1.0 implementation.
        }
        void await_resume() const {}
        void await_suspend(coroutine_handle<> handle) const { ... }
    };
    return awaitable{ ... };
};

현재와 이전의 이 동작은 Win32 애플리케이션 개발에서 PostMessageSendMessage 사이의 차이와 비슷합니다.This current, versus previous, behavior is analogous to the difference between PostMessage and SendMessage in Win32 application development. PostMessage는 작업을 큐에 넣은 다음, 작업이 완료될 때까지 기다리지 않고 스택을 해제합니다.PostMessage queues the work and then unwinds the stack without waiting for the work to complete. 스택 해제는 필수적일 수 있습니다.The stack-unwinding can be essential.

winrt::resume_foreground 함수도 처음에는 Windows 10 이전에 도입된 CoreDispatcher(CoreWindow에 연결됨)만 지원했습니다.The winrt::resume_foreground function also initially only supported the CoreDispatcher (tied to a CoreWindow), which was introduced prior to Windows 10. 그 이후 더 유연하고 효율적인 DispatcherQueue 디스패처를 도입했습니다.We've since introduced a more flexible and efficient dispatcher: the DispatcherQueue. DispatcherQueue는 사용자 고유의 용도에 맞게 만들 수 있습니다.You can create a DispatcherQueue for your own purposes. 다음과 같은 간단한 콘솔 애플리케이션을 살펴보겠습니다.Consider this simple console application.

using namespace Windows::System;

winrt::fire_and_forget RunAsync(DispatcherQueue queue);
 
int main()
{
    auto controller{ DispatcherQueueController::CreateOnDedicatedThread() };
    RunAsync(controller.DispatcherQueue());
    getchar();
}

위의 예제에서는 큐(컨트롤러 내에 포함되어 있음)를 프라이빗 스레드에 만든 다음, 해당 컨트롤러를 코루틴에 전달합니다.The example above creates a queue (contained within a controller) on a private thread, and then passes the controller to the coroutine. 코루틴은 큐를 사용하여 프라이빗 스레드에서 대기(일시 중단 및 다시 시작)할 수 있습니다.The coroutine can use the queue to await (suspend and resume) on the private thread. DispatcherQueue의 또 다른 일반적인 용도는 큐를 기존 데스크톱 또는 Win32 앱의 현재 UI 스레드에 만드는 것입니다.Another common use of DispatcherQueue is to create a queue on the current UI thread for a traditional desktop or Win32 app.

DispatcherQueueController CreateDispatcherQueueController()
{
    DispatcherQueueOptions options
    {
        sizeof(DispatcherQueueOptions),
        DQTYPE_THREAD_CURRENT,
        DQTAT_COM_STA
    };
 
    ABI::Windows::System::IDispatcherQueueController* ptr{};
    winrt::check_hresult(CreateDispatcherQueueController(options, &ptr));
    return { ptr, take_ownership_from_abi };
}

여기서는 Win32 스타일의 CreateDispatcherQueueController 함수를 호출하여 컨트롤러를 만든 다음, 결과 큐 컨트롤러의 소유권을 WinRT 개체로 호출자에 이전함으로써 Win32 함수를 호출하여 C++/WinRT 프로젝트에 통합하는 방법을 보여 줍니다.This illustrates how you can call and incorporate Win32 functions into your C++/WinRT projects, by simply calling the Win32-style CreateDispatcherQueueController function to create the controller, and then transfer ownership of the resulting queue controller to the caller as a WinRT object. 또한 이를 통해 기존 Petzold 스타일의 Win32 데스크톱 애플리케이션에서 효율적이고 원활한 큐를 지원할 수 있습니다.This is also precisely how you can support efficient and seamless queuing on your existing Petzold-style Win32 desktop application.

winrt::fire_and_forget RunAsync(DispatcherQueue queue);
 
int main()
{
    Window window;
    auto controller{ CreateDispatcherQueueController() };
    RunAsync(controller.DispatcherQueue());
    MSG message;
 
    while (GetMessage(&message, nullptr, 0, 0))
    {
        DispatchMessage(&message);
    }
}

위의 간단한 main 함수는 창을 만드는 것으로 시작합니다.Above, the simple main function begins by creating a window. 이 경우 창 클래스를 등록하고 CreateWindow를 호출하여 최상위 데스크톱 창을 만드는 것으로 생각할 수 있습니다.You can imagine that this registers a window class, and calls CreateWindow to create the top-level desktop window. 다음으로, CreateDispatcherQueueController 함수를 호출하여 큐 컨트롤러를 만든 후에 이 컨트롤러에서 소유한 디스패처 큐를 사용하여 일부 코루틴을 호출합니다.CreateDispatcherQueueController function is then called to create the queue controller before calling some coroutine with the dispatcher queue owned by this controller. 그런 다음, 일반적인 메시지 펌프가 입력되어 이 스레드에서 코루틴을 자연스럽게 다시 시작합니다.A traditional message pump is then entered where resumption of the coroutine naturally occurs on this thread. 이렇게 하면 애플리케이션 내의 비동기 또는 메시지 기반 워크플로에 대한 세련된 코루틴 세계로 돌아갈 수 있습니다.Having done that, you can return to the elegant world of coroutines for your async or message-based workflow within your application.

winrt::fire_and_forget RunAsync(DispatcherQueue queue)
{
    ... // Begin on the calling thread...
 
    co_await winrt::resume_foreground(queue);
 
    ... // ...resume on the dispatcher thread.
}

winrt::resume_foreground에 대한 호출은 항상 에 대기한 다음, 스택을 해제합니다.The call to winrt::resume_foreground will always queue, and then unwind the stack. 필요에 따라 재시작 우선 순위를 설정할 수도 있습니다.You can also optionally set the resumption priority.

winrt::fire_and_forget RunAsync(DispatcherQueue queue)
{
    ...
 
    co_await winrt::resume_foreground(queue, DispatcherQueuePriority::High);
 
    ...
}

또는 기본 큐 순서를 사용합니다.Or, using the default queuing order.

winrt::fire_and_forget RunAsync(DispatcherQueue queue)
{
    ...
 
    co_await queue;
 
    ...
}

또는 이 경우 큐 종료를 검색하여 정상적으로 처리합니다.Or, in this case detecting queue shutdown, and handling it gracefully.

winrt::fire_and_forget RunAsync(DispatcherQueue queue)
{
    ...
 
    if (co_await queue)
    {
        ... // Resume on dispatcher thread.
    }
    else
    {
        ... // Still on calling thread.
    }
}

co_await 식에서 true를 반환하며, 이는 디스패처 스레드에서 다시 시작됨을 나타냅니다.The co_await expression returns true, indicating that resumption will occur on the dispatcher thread. 즉 해당 큐가 성공적으로 완료되었습니다.In other words, that queuing was successful. 반대로, 큐 컨트롤러가 종료되고 큐 요청을 더 이상 처리하지 않으므로 실행이 호출 스레드에 남아 있음을 나타내기 위해 false를 반환합니다.Conversely, it returns false to indicate that execution remains on the calling thread because the queue's controller is shutting down and is no longer serving queue requests.

따라서 C++/WinRT를 코루틴과 결합하는 경우, 특히 오래된 Petzold 스타일의 데스크톱 애플리케이션 개발을 수행할 때 다양한 기능을 손쉽게 이용할 수 있습니다.So, you have a great deal of power at your fingertips when you combine C++/WinRT with coroutines; and especially when doing some old-school Petzold-style desktop application development.

비동기 작업 취소 및 취소 콜백Canceling an asynchronous operation, and cancellation callbacks

비동기 프로그래밍을 위한 Windows 런타임 기능을 사용하면 진행 중인 비동기 작업을 취소할 수 있습니다.The Windows Runtime's features for asynchronous programming allow you to cancel an in-flight asynchronous action or operation. 다음은 StorageFolder::GetFilesAsync를 호출하여 잠재적으로 큰 파일 컬렉션을 검색하고 결과 비동기 작업 개체를 데이터 멤버에 저장하는 예제입니다.Here's an example that calls StorageFolder::GetFilesAsync to retrieve a potentially large collection of files, and it stores the resulting asynchronous operation object in a data member. 사용자가 작업을 취소할 수 있습니다.The user has the option to cancel the operation.

// MainPage.xaml
...
<Button x:Name="workButton" Click="OnWork">Work</Button>
<Button x:Name="cancelButton" Click="OnCancel">Cancel</Button>
...

// MainPage.h
...
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Storage.Search.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Storage;
using namespace Windows::Storage::Search;
using namespace Windows::UI::Xaml;
...
struct MainPage : MainPageT<MainPage>
{
    MainPage()
    {
        InitializeComponent();
    }

    IAsyncAction OnWork(IInspectable /* sender */, RoutedEventArgs /* args */)
    {
        workButton().Content(winrt::box_value(L"Working..."));

        // Enable the Pictures Library capability in the app manifest file.
        StorageFolder picturesLibrary{ KnownFolders::PicturesLibrary() };

        m_async = picturesLibrary.GetFilesAsync(CommonFileQuery::OrderByDate, 0, 1000);

        IVectorView<StorageFile> filesInFolder{ co_await m_async };

        workButton().Content(box_value(L"Done!"));

        // Process the files in some way.
    }

    void OnCancel(IInspectable const& /* sender */, RoutedEventArgs const& /* args */)
    {
        if (m_async.Status() != AsyncStatus::Completed)
        {
            m_async.Cancel();
            workButton().Content(winrt::box_value(L"Canceled"));
        }
    }

private:
    IAsyncOperation<::IVectorView<StorageFile>> m_async;
};
...

취소 구현 측면의 경우 간단한 예제로 시작하겠습니다.For the implementation side of cancellation, let's begin with a simple example.

// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace std::chrono_literals;

IAsyncAction ImplicitCancellationAsync()
{
    while (true)
    {
        std::cout << "ImplicitCancellationAsync: do some work for 1 second" << std::endl;
        co_await 1s;
    }
}

IAsyncAction MainCoroutineAsync()
{
    auto implicit_cancellation{ ImplicitCancellationAsync() };
    co_await 3s;
    implicit_cancellation.Cancel();
}

int main()
{
    winrt::init_apartment();
    MainCoroutineAsync().get();
}

위의 예제를 실행하면 ImplicitCancellationAsync가 3초간 초당 1개 메시지를 출력하며, 이후에는 취소 결과로 자동 종료됩니다.If you run the example above, then you'll see ImplicitCancellationAsync print one message per second for three seconds, after which time it automatically terminates as a result of being canceled. co_await 식을 발견할 경우 코루틴이 취소 여부를 확인하기 때문에 이 코드는 제대로 실행됩니다.This works because, on encountering a co_await expression, a coroutine checks whether it has been cancelled. 취소된 경우 단락되고, 취소되지 않은 경우 정상적으로 일시 중단됩니다.If it has, then it short-circuits out; and if it hasn't, then it suspends as normal.

코루틴이 일시 중단된 동안 취소가 발생할 수도 있습니다.Cancellation can, of course, happen while the coroutine is suspended. 코루틴이 다시 시작되거나 다른 co_await를 적중하는 경우에만 취소를 확인합니다.Only when the coroutine resumes, or hits another co_await, will it check for cancellation. 잠재적 문제 중 하나는 취소 응답 시 너무 성긴 대기 시간입니다.The issue is one of potentially too-coarse-grained latency in responding to cancellation.

따라서 또 다른 옵션은 코루틴 내에서 취소를 명시적으로 폴링하는 것입니다.So, another option is to explicitly poll for cancellation from within your coroutine. 위의 예제를 아래 목록의 코드로 업데이트합니다.Update the example above with the code in the listing below. 이 새로운 예제에서 ExplicitCancellationAsyncwinrt::get_cancellation_token 함수에서 반환된 개체를 검색하고, 이 개체를 사용하여 코루틴의 취소 여부를 정기적으로 검사합니다.In this new example, ExplicitCancellationAsync retrieves the object returned by the winrt::get_cancellation_token function, and uses it to periodically check whether the coroutine has been canceled. 취소되지 않는 한, 코루틴이 무기한 반복됩니다. 취소되면, 루프와 함수가 정상적으로 종료됩니다.As long as it's not canceled, the coroutine loops indefinitely; once it is canceled, the loop and the function exit normally. 결과는 이전 예제와 동일하지만, 여기서는 종료가 명시적으로 발생하고 제어됩니다.The outcome is the same as the previous example, but here exiting happens explicitly, and under control.

IAsyncAction ExplicitCancellationAsync()
{
    auto cancellation_token{ co_await winrt::get_cancellation_token() };

    while (!cancellation_token())
    {
        std::cout << "ExplicitCancellationAsync: do some work for 1 second" << std::endl;
        co_await 1s;
    }
}

IAsyncAction MainCoroutineAsync()
{
    auto explicit_cancellation{ ExplicitCancellationAsync() };
    co_await 3s;
    explicit_cancellation.Cancel();
}
...

winrt::get_cancellation_token을 기다리면 코루틴에서 자동으로 생성하는 IAsyncAction 정보와 함께 취소 토큰이 검색됩니다.Waiting on winrt::get_cancellation_token retrieves a cancellation token with knowledge of the IAsyncAction that the coroutine is producing on your behalf. 해당 토큰의 함수 호출 연산자를 사용하여 취소 상태를 쿼리할 수 있습니다(근본적으로 취소 폴링).You can use the function call operator on that token to query the cancellation state—essentially polling for cancellation. 컴퓨팅 바인딩된 일부 작업을 수행하거나 큰 컬렉션을 반복하는 경우에 적합한 기술입니다.If you're performing some compute-bound operation, or iterating through a large collection, then this is a reasonable technique.

취소 콜백 등록Register a cancellation callback

Windows 런타임의 취소는 다른 비동기 개체로 자동으로 이동하지 않습니다.The Windows Runtime's cancellation doesn't automatically flow to other asynchronous objects. 그러나 Windows SDK 버전 10.0.17763.0(Windows 10, 버전 1809)부터 취소 콜백을 등록할 수 있습니다.But—introduced in version 10.0.17763.0 (Windows 10, version 1809) of the Windows SDK—you can register a cancellation callback. 취소를 전파할 수 있는 선점형 후크인 이 취소 콜백을 사용하면 기존 동시성 라이브러리와 통합할 수 있습니다.This is a pre-emptive hook by which cancellation can be propagated, and makes it possible to integrate with existing concurrency libraries.

다음 코드 예제에서 NestedCoroutineAsync는 작업을 수행하지만, 특별한 취소 논리가 없습니다.In this next code example, NestedCoroutineAsync does the work, but it has no special cancellation logic in it. CancellationPropagatorAsync는 근본적으로 중첩 코루틴의 래퍼입니다. 이 래퍼는 선점 방식으로 취소를 전달합니다.CancellationPropagatorAsync is essentially a wrapper on the nested coroutine; the wrapper forwards cancellation pre-emptively.

// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace std::chrono_literals;

IAsyncAction NestedCoroutineAsync()
{
    while (true)
    {
        std::cout << "NestedCoroutineAsync: do some work for 1 second" << std::endl;
        co_await 1s;
    }
}

IAsyncAction CancellationPropagatorAsync()
{
    auto cancellation_token{ co_await winrt::get_cancellation_token() };
    auto nested_coroutine{ NestedCoroutineAsync() };

    cancellation_token.callback([=]
    {
        nested_coroutine.Cancel();
    });

    co_await nested_coroutine;
}

IAsyncAction MainCoroutineAsync()
{
    auto cancellation_propagator{ CancellationPropagatorAsync() };
    co_await 3s;
    cancellation_propagator.Cancel();
}

int main()
{
    winrt::init_apartment();
    MainCoroutineAsync().get();
}

CancellationPropagatorAsync는 고유한 취소 콜백에 대해 람다 함수를 등록한 다음, 중첩 작업이 완료될 때까지 기다립니다(일시 중단).CancellationPropagatorAsync registers a lambda function for its own cancellation callback, and then it awaits (it suspends) until the nested work completes. CancellationPropagatorAsync가 취소되는 경우 취소를 중첩 코루틴으로 전파합니다.When or if CancellationPropagatorAsync is canceled, it propagates the cancellation to the nested coroutine. 취소를 폴링할 필요가 없으며, 취소가 무기한 차단되지도 않습니다.There's no need to poll for cancellation; nor is cancellation blocked indefinitely. 이 메커니즘은 C++/WinRT를 전혀 모르는 코루틴 또는 동시성 라이브러리와 상호 운용하는 데 사용할 수 있을 만큼 유연합니다.This mechanism is flexible enough for you to use it to interop with a coroutine or concurrency library that knows nothing of C++/WinRT.

진행 상황 보고Reporting progress

코루틴이 IAsyncActionWithProgress 또는 IAsyncOperationWithProgress를 반환하는 경우 winrt::get_progress_token 함수에서 반환된 개체를 검색하고, 이 개체를 사용하여 진행률 처리기에 진행 상황을 다시 보고할 수 있습니다.If your coroutine returns either IAsyncActionWithProgress, or IAsyncOperationWithProgress, then you can retrieve the object returned by the winrt::get_progress_token function, and use it to report progress back to a progress handler. 코드 예제는 다음과 같습니다.Here's a code example.

// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace std::chrono_literals;

IAsyncOperationWithProgress<double, double> CalcPiTo5DPs()
{
    auto progress{ co_await winrt::get_progress_token() };

    co_await 1s;
    double pi_so_far{ 3.1 };
    progress(0.2);

    co_await 1s;
    pi_so_far += 4.e-2;
    progress(0.4);

    co_await 1s;
    pi_so_far += 1.e-3;
    progress(0.6);

    co_await 1s;
    pi_so_far += 5.e-4;
    progress(0.8);

    co_await 1s;
    pi_so_far += 9.e-5;
    progress(1.0);
    co_return pi_so_far;
}

IAsyncAction DoMath()
{
    auto async_op_with_progress{ CalcPiTo5DPs() };
    async_op_with_progress.Progress([](auto const& /* sender */, double progress)
    {
        std::wcout << L"CalcPiTo5DPs() reports progress: " << progress << std::endl;
    });
    double pi{ co_await async_op_with_progress };
    std::wcout << L"CalcPiTo5DPs() is complete !" << std::endl;
    std::wcout << L"Pi is approx.: " << pi << std::endl;
}

int main()
{
    winrt::init_apartment();
    DoMath().get();
}

참고

비동기 작업에 대해 ‘완료 처리기’를 둘 이상 구현하는 것은 올바르지 않습니다. It's not correct to implement more than one completion handler for an asynchronous action or operation. 완료 이벤트에 단일 대리자를 사용하거나 완료 이벤트를 co_await할 수 있습니다.You can have either a single delegate for its completed event, or you can co_await it. 둘 다 사용하면 두 번째는 실패합니다.If you have both, then the second will fail. 다음 두 종류의 완료 처리기 중 하나만 사용해야 하며, 동일한 비동기 개체에 대해 둘 다 사용하면 안 됩니다.Either one of the following two kinds of completion handlers is appropriate; not both for the same async object.

auto async_op_with_progress{ CalcPiTo5DPs() };
async_op_with_progress.Completed([](auto const& sender, AsyncStatus /* status */)
{
    double pi{ sender.GetResults() };
});
auto async_op_with_progress{ CalcPiTo5DPs() };
double pi{ co_await async_op_with_progress };

완료 처리기에 대한 자세한 내용은 비동기 작업을 위한 대리자 형식을 참조하세요.For more info about completion handlers, see Delegate types for asynchronous actions and operations.

시작 후 망각형(Fire and Forget)Fire and forget

다른 작업과 동시에 수행할 수 있는 작업이 있고, 해당 작업이 완료되기를 기다리거나(종속된 다른 작업이 없음) 작업에서 값이 반환될 필요가 없는 경우도 있습니다.Sometimes, you have a task that can be done concurrently with other work, and you don't need to wait for that task to complete (no other work depends on it), nor do you need it to return a value. 이 경우 작업을 시작하고 망각할 수 있습니다.In that case, you can fire off the task and forget it. 반환 형식이 Windows 런타임 비동기 작업 유형 중 하나 또는 concurrency::task가 아니라 winrt::fire_and_forget인 코루틴을 작성하면 됩니다.You can do that by writing a coroutine whose return type is winrt::fire_and_forget (instead of one of the Windows Runtime asynchronous operation types, or concurrency::task).

// main.cpp
#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace std::chrono_literals;

winrt::fire_and_forget CompleteInFiveSeconds()
{
    co_await 5s;
}

int main()
{
    winrt::init_apartment();
    CompleteInFiveSeconds();
    // Do other work here.
}

winrt::fire_and_forget은 비동기 작업을 수행해야 할 때 이벤트 처리기의 반환 형식으로도 유용합니다.winrt::fire_and_forget is also useful as the return type of your event handler when you need to perform asynchronous operations in it. 다음은 예제입니다(C++/WinRT의 강한 참조 및 약한 참조 참조).Here's an example (also see Strong and weak references in C++/WinRT).

winrt::fire_and_forget MyClass::MyMediaBinder_OnBinding(MediaBinder const&, MediaBindingEventArgs args)
{
    auto lifetime{ get_strong() }; // Prevent *this* from prematurely being destructed.
    auto ensure_completion{ unique_deferral(args.GetDeferral()) }; // Take a deferral, and ensure that we complete it.

    auto file{ co_await StorageFile::GetFileFromApplicationUriAsync(Uri(L"ms-appx:///video_file.mp4")) };
    args.SetStorageFile(file);

    // The destructor of unique_deferral completes the deferral here.
}

첫 번째 인수(sender)는 사용하지 않으므로 명명되지 않은 그대로입니다.The first argument (the sender) is left unnamed, because we never use it. 따라서 이 인수는 참조로 두어도 안전합니다.For that reason we're safe to leave it as a reference. 하지만 args는 값으로 전달됩니다.But observe that args is passed by value. 위의 매개 변수 전달 섹션을 참조하세요.See the Parameter-passing section above.

커널 핸들 대기Awaiting a kernel handle

C++/WinRT는 커널 이벤트에서 신호를 받을 때까지 일시 중단하는 데 사용할 수 있는 resume_on_signal 클래스를 제공합니다.C++/WinRT provides a resume_on_signal class, which you can use to suspend until a kernel event is signaled. co_await resume_on_signal(h)이 반환될 때까지 핸들이 계속 유효하게 유지되는지 확인해야 합니다.You're responsible for ensuring that the handle remains valid until your co_await resume_on_signal(h) returns. 이 첫 번째 예제와 같이 resume_on_signal이 시작되기도 전에 핸들이 손실되었을 수 있으므로 resume_on_signal 자체는 이 작업을 수행할 수 없습니다.resume_on_signal itself can't do that for you, because you may have lost the handle even before the resume_on_signal starts, as in this first example.

IAsyncAction Async(HANDLE event)
{
    co_await DoWorkAsync();
    co_await resume_on_signal(event); // The incoming handle is not valid here.
}

들어오는 HANDLE은 함수가 반환될 때까지 유효하며, 이 함수(코루틴)는 첫 번째 일시 중단 지점(이 경우 첫 번째 co_await)에서 반환됩니다.The incoming HANDLE is valid only until the function returns, and this function (which is a coroutine) returns at the first suspension point (the first co_await in this case). DoWorkAsync를 기다리는 동안 컨트롤이 호출자에 반환되고, 호출하는 프레임이 범위를 벗어났으며, 코루틴이 다시 시작될 때 핸들이 유효한지 여부를 더 이상 알 수 없습니다.While awaiting DoWorkAsync, control has returned to the caller, the calling frame has gone out of scope, and you no longer know whether the handle will be valid when your coroutine resumes.

기술적으로 코루틴은 매개 변수를 값으로 받고 있습니다(위의 매개 변수 전달 참조).Technically, our coroutine is receiving its parameters by value, as it should (see Parameter-passing above). 그러나 이 경우 해당 지침의 정신(단지 문자만이 아닌)을 따르기 위해 한 단계 더 나아가야 합니다.But in this case we need to go a step further so that we're following the spirit of that guidance (rather than just the letter). 핸들과 함께 강한 참조(즉, 소유권)를 전달해야 합니다.We need to pass a strong reference (in other words, ownership) along with the handle. 다음과 같이 하세요.Here's how.

IAsyncAction Async(winrt::handle event)
{
    co_await DoWorkAsync();
    co_await resume_on_signal(event); // The incoming handle *is* not valid here.
}

값을 기준으로 winrt::handle을 전달하면 소유권 의미 체계가 제공되므로 커널 핸들이 코루틴의 수명 동안 유효하게 유지됩니다.Passing a winrt::handle by value provides ownership semantics, which ensures that the kernel handle remains valid for the lifetime of the coroutine.

이 코루틴을 호출하는 방법은 다음과 같습니다.Here's how you might call that coroutine.

namespace
{
    winrt::handle duplicate(winrt::handle const& other, DWORD access)
    {
        winrt::handle result;
        if (other)
        {
            winrt::check_bool(::DuplicateHandle(::GetCurrentProcess(),
                other.get(), ::GetCurrentProcess(), result.put(), access, FALSE, 0));
        }
        return result;
    }

    winrt::handle make_manual_reset_event(bool initialState = false)
    {
        winrt::handle event{ ::CreateEvent(nullptr, true, initialState, nullptr) };
        winrt::check_bool(static_cast<bool>(event));
        return event;
    }
}

IAsyncAction SampleCaller()
{
    handle event{ make_manual_reset_event() };
    auto async{ Async(duplicate(event)) };

    ::SetEvent(event.get());
    event.close(); // Our handle is closed, but Async still has a valid handle.

    co_await async; // Will wake up when *event* is signaled.
}

간편한 비동기 시간 제한Asynchronous timeouts made easy

C++/WinRT는 C++ 코루틴에 많이 투자됩니다.C++/WinRT is invested heavily in C++ coroutines. 동시성 코드 작성에 미치는 영향력은 다양합니다.Their effect on writing concurrency code is transformational. 이 섹션에서는 비동기에 대한 세부 정보가 중요하지 않은 경우에 대해 설명하며, 원하는 것은 결과뿐입니다.This section discusses cases where details of asynchrony are not important, and all you want is the result there and then. 이러한 이유로 IAsyncAction Windows 런타임 비동기 작업 인터페이스에 대한 C++/WinRT의 구현에는 std::function에서 제공하는 것과 비슷한 get 함수가 있습니다.For that reason, C++/WinRT's implementation of the IAsyncAction Windows Runtime asynchronous operation interface has a get function, similar to that provided by std::function.

using namespace winrt::Windows::Foundation;
int main()
{
    IAsyncAction async = ...
    async.get();
    puts("Done!");
}

비동기 개체가 완료되는 동안 get 함수는 무기한 차단됩니다.The get function blocks indefinitely, while the async object completes. 비동기 개체는 수명이 매우 짧으므로 필요한 경우가 많습니다.Async objects tend to be very short-lived, so this is often all you need.

그러나 이것만으로 충분하지 않은 경우가 있으며, 시간이 좀 경과되면 대기를 중단해야 합니다.But there are cases where that's not sufficient, and you need to abandon the wait after some time has elapsed. 이 코드는 언제든지 Windows 런타임에서 제공하는 구성 요소를 통해 작성할 수 있었습니다.Writing that code has always been possible, thanks to the building blocks provided by the Windows Runtime. 그러나 이제 C++/WinRT를 사용하면 wait_for 함수를 제공하여 훨씬 쉽게 처리할 수 있습니다.But now C++/WinRT makes it a lot easier by providing the wait_for function. 또한 IAsyncAction에서도 구현되며, std::function에서 제공하는 것과 비슷합니다.It's also implementated on IAsyncAction, and again it's similar to that provided by std::function.

using namespace std::chrono_literals;
int main()
{
    IAsyncAction async = ...
 
    if (async.wait_for(5s) == AsyncStatus::Completed)
    {
        puts("done");
    }
}

참고

wait_for 함수는 인터페이스에서 std::chrono::duration을 사용하지만, std::chrono::duration이 제공하는 범위보다 작은 범위로 제한됩니다(약 49.7일).wait_for uses std::chrono::duration at the interface, but it is limited to some range smaller than what std::chrono::duration provides (roughly 49.7 days).

다음 예제의 wait_for는 약 5초 동안 기다린 후에 완료를 확인합니다.The wait_for in this next example waits for around five seconds and then it checks completion. 비교가 양호하면 비동기 개체가 성공적으로 완료되었음을 알 수 있습니다.If the comparison is favorable, then you know that the async object completed successfully, and you're done. 일부 결과를 기다리는 경우 get 함수를 호출하여 결과를 검색하기만 하면 됩니다.If you're waiting for some result, then you can simply follow that with a call to the get function to retrieve the result.

int main()
{
    IAsyncOperation<int> async = ...
 
    if (async.wait_for(5s) == AsyncStatus::Completed)
    {
        printf("result %d\n", async.get());
    }
}

비동기 개체가 그때까지 완료되었으므로 get은 더 이상 기다리지 않고 결과를 즉시 반환합니다.Because the async object has completed by then, get returns the result immediately, without any further wait. 여기서 볼 수 있듯이 wait_for는 비동기 개체의 상태를 반환합니다.As you can see, wait_for returns the state of the async object. 따라서 이 상태는 다음과 같이 더 세분화된 제어에 사용할 수 있습니다.So, you can use it for more fine-grained control, like this.

switch (async.wait_for(5s))
{
case AsyncStatus::Completed:
    printf("result %d\n", async.get());
    break;
case AsyncStatus::Canceled:
    puts("canceled");
    break;
case AsyncStatus::Error:
    puts("failed");
    break;
case AsyncStatus::Started:
    puts("still running");
    break;
}
  • AsyncStatus::Completed는 비동기 개체가 성공적으로 완료되었음을 의미하며, get 함수를 호출하여 결과를 검색할 수 있습니다.Remember that AsyncStatus::Completed means that the async object completed successfully, and you may call the get function to retrieve any result.
  • AsyncStatus::Canceled는 비동기 개체가 취소되었음을 의미합니다.AsyncStatus::Canceled means that the async object was canceled. 취소는 일반적으로 호출자가 요청하므로 이 상태를 처리하는 경우는 거의 없습니다.A cancellation is typically requested by the caller, so it would be rare to handle this state. 일반적으로 취소된 비동기 개체는 간단히 삭제됩니다.Typically, a cancelled async object is simply discarded.
  • AsyncStatus::Error는 비동기 개체가 어떤 방법으로든 실패했음을 의미합니다.AsyncStatus::Error means that the async object has failed in some way. 원하는 경우 예외를 다시 throw하기 위해 get을 수행할 수 있습니다.You can get to rethrow the exception if you wish.
  • AsyncStatus::Started는 비동기 개체가 아직도 실행되고 있음을 의미합니다.AsyncStatus::Started means that the async object is still running. Windows 런타임 비동기 패턴은 여러 대기와 대기자를 허용하지 않습니다.The Windows Runtime async pattern doesn't allow multiple waits, nor waiters. 즉 루프에서 wait_for를 호출할 수 없습니다.That means that you can't call wait_for in a loop. 대기 시간이 효과적으로 초과되면 몇 가지 선택 사항이 있습니다.If the wait has effectively timed-out, then you're left with a few choices. 개체를 중단하거나, get을 호출하기 전에 해당 상태를 폴링하여 결과를 검색할 수 있습니다.You can abandon the object, or you can poll its status before calling get to retrieve any result. 그러나 이 시점에서 개체를 삭제하는 것이 가장 좋습니다.But it's best just to discard the object at this point.

비동기식으로 배열 반환Returning an array asynchronously

다음은 error MIDL2025: [msg]syntax error [context]: expecting > or, near "[" 오류를 생성하는 MIDL 3.0 예제입니다.Below is an example of MIDL 3.0 that produces error MIDL2025: [msg]syntax error [context]: expecting > or, near "[".

Windows.Foundation.IAsyncOperation<Int32[]> RetrieveArrayAsync();

오류가 발생하는 이유는 배열을 매개 변수가 있는 인터페이스의 매개 변수 형식 인수로 사용할 수 없기 때문입니다.The reason is that it's invalid to use an array as a parameter type argument to a parameterized interface. 따라서 런타임 클래스 메서드에서 배열을 다시 비동기적으로 전달한다는 목표를 보다 덜 명확하게 달성할 수 있는 방법이 필요합니다.So we need a less obvious way to achieve the aim of asynchronously passing an array back from a runtime class method.

PropertyValue 개체에 boxing된 배열을 반환할 수 있습니다.You can return the array boxed into a PropertyValue object. 그러면 호출 코드에서 배열을 unboxing합니다.The calling code then unboxes it. 다음은 SampleComponent 런타임 클래스를 Windows 런타임 구성 요소(C++/WinRT) 프로젝트에 추가한 다음, Core 앱(C++/WinRT) 등의 프로젝트에서 사용해 볼 수 있는 코드 예제입니다.Here's a code example, which you can try out by adding the SampleComponent runtime class to a Windows Runtime Component (C++/WinRT) project, and then consuming that from (for example) a Core App (C++/WinRT) project.

// SampleComponent.idl
namespace MyComponentProject
{
    runtimeclass SampleComponent
    {
        Windows.Foundation.IAsyncOperation<IInspectable> RetrieveCollectionAsync();
    };
}

// SampleComponent.h
...
struct SampleComponent : SampleComponentT<SampleComponent>
{
    ...
    Windows::Foundation::IAsyncOperation<Windows::Foundation::IInspectable> RetrieveCollectionAsync()
    {
        co_return Windows::Foundation::PropertyValue::CreateInt32Array({ 99, 101 }); // Box an array into a PropertyValue.
    }
}
...

// SampleCoreApp.cpp
...
MyComponentProject::SampleComponent m_sample_component;
...
auto boxed_array{ co_await m_sample_component.RetrieveCollectionAsync() };
auto property_value{ boxed_array.as<winrt::Windows::Foundation::IPropertyValue>() };
winrt::com_array<int32_t> my_array;
property_value.GetInt32Array(my_array); // Unbox back into an array.
...

중요 APIImportant APIs