Concorrenza e asincronia più avanzate con C++/WinRTMore advanced concurrency and asynchrony with C++/WinRT

Questo argomento descrive scenari più avanzati di concorrenza e asincronia in C++/WinRT.This topic describes more advanced scenarios with concurrency and asynchrony in C++/WinRT.

Per un'introduzione a questo argomento, leggi prima Concorrenza e operazioni asincrone.For an introduction to this subject, first read Concurrency and asynchronous operations.

Operazioni di offload nel pool di thread di WindowsOffloading work onto the Windows thread pool

Una coroutine è una funzione come qualsiasi altra in cui un chiamante viene bloccato fino a quando una funzione restituisce l'esecuzione al chiamante.A coroutine is a function like any other in that a caller is blocked until a function returns execution to it. La prima opportunità che ha la coroutine di restituire il controllo è la prima occorrenza di co_await, co_return o co_yield.And, the first opportunity for a coroutine to return is the first co_await, co_return, or co_yield.

Quindi, prima di eseguire le operazioni di associazione del calcolo (compute-bound) in una coroutine, è necessario restituire l'esecuzione al chiamante affinché non venga bloccato, inserendo in altre parole un punto di sospensione.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. Se ciò non è già in corso tramite l'uso di co_await per un'altra operazione, è possibile eseguire co_await per la funzione winrt::resume_background.If you're not already doing that by co_await-ing some other operation, then you can co_await the winrt::resume_background function. Viene così restituito il controllo al chiamante e quindi riprende immediatamente l'esecuzione in un thread del relativo pool.That returns control to the caller, and then immediately resumes execution on a thread pool thread.

Il pool di thread usato nell'implementazione è il pool di thread Windows di basso livello, per un'efficienza ottimale.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;
}

Programmazione basata sull'affinità del threadProgramming with thread affinity in mind

Questo scenario si basa su quello precedente.This scenario expands on the previous one. Si esegue l'offload di alcune operazioni nel pool di thread, ma poi si vuole visualizzare l'avanzamento nell'interfaccia utente.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.
}

Il codice precedente genera un'eccezione winrt::hresult_wrong_thread, perché un elemento TextBlock deve essere aggiornato dal thread che lo ha creato, ovvero il thread dell'interfaccia utente.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. Una soluzione consiste nell'acquisire il contesto del thread in cui è stata originariamente chiamata la coroutine.One solution is to capture the thread context within which our coroutine was originally called. A tale scopo, crea un'istanza di un oggetto winrt::apartment_context, esegui le operazioni in background e quindi usa co_await per apartment_context per tornare al contesto chiamante.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.
}

Se la coroutine precedente è chiamata dal thread dell'interfaccia utente che ha creato TextBlock, questa tecnica funziona.As long as the coroutine above is called from the UI thread that created the TextBlock, then this technique works. Di ciò potrai avere la certezza in numerosi casi relativi alla tua app.There will be many cases in your app where you're certain of that.

Per una soluzione più generica per l'aggiornamento dell'interfaccia utente, adatta ai casi in cui non sei certo del thread chiamante, puoi usare co_await per la funzione winrt::resume_foreground per passare a uno specifico thread in primo piano.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. Nell'esempio di codice riportato di seguito, specifichiamo il thread in primo piano passando l'oggetto dispatcher associato a TextBlock (accedendo alla relativa proprietà Dispatcher ).In the code example below, we specify the foreground thread by passing the dispatcher object associated with the TextBlock (by accessing its Dispatcher property). L'implementazione di winrt::resume_foreground chiama CoreDispatcher.RunAsync su tale oggetto dispatcher per eseguire le operazioni successive nella coroutine.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.

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

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

La funzione winrt::resume_foreground accetta un parametro di priorità facoltativo.The winrt::resume_foreground function takes an optional priority parameter. Se si usa questo parametro, il modello illustrato in precedenza è appropriato.If you're using that parameter, then the pattern shown above is appropriate. In caso contrario, è possibile scegliere di semplificare co_await winrt::resume_foreground(someDispatcherObject); con co_await someDispatcherObject;.If not, then you can choose to simplify co_await winrt::resume_foreground(someDispatcherObject); into just co_await someDispatcherObject;.

Contesti di esecuzione, ripresa e cambio di contesto in una coroutineExecution contexts, resuming, and switching in a coroutine

In termini generali, dopo un punto di sospensione in una coroutine, il thread di esecuzione originale potrebbe essere rilasciato e la ripresa potrebbe avvenire su qualsiasi thread. In altre parole, qualsiasi thread può chiamare il metodo Completed per l'operazione asincrona.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).

Se usi co_await per uno qualsiasi dei quattro tipi di operazione asincrona di Windows Runtime (IAsyncXxx), tuttavia, C++/WinRT acquisisce il contesto di chiamata in corrispondenza di co_awaitBut 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. e si assicura che tale contesto sia ancora attivo al momento della ripresa.And it ensures that you're still on that context when the continuation resumes. A tale scopo, C++/WinRT controlla che tu sia già nel contesto di chiamata e se non è così passa a tale contesto.C++/WinRT does this by checking whether you're already on the calling context and, if not, switching to it. Se è attivo un thread in modalità apartment a thread singolo prima di co_await, sarai nello stesso in seguito. In modo analogo, se il thread prima di co_await è multithreading, verrà usato lo stesso anche in un secondo momento.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.
}

Il motivo per cui puoi contare su questo comportamento è che C++/WinRT fornisce il codice per adattare questi tipi di operazione asincrona di Windows Runtime per il supporto del linguaggio per le coroutine C++ (questi frammenti di codice sono noti come adapter di attesa).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). I tipi awaitable rimanenti in C++/WinRT sono semplicemente wrapper di pool di thread e/o helper. Il completamento avviene pertanto nel pool di thread.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;
}

Se usi co_await per un altro tipo, anche all'interno di un'implementazione di coroutine C++/WinRT, un'altra libreria fornisce gli adapter e dovrai capire cosa fanno tali adapter in relazione alla ripresa e ai contesti.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.

Per ridurre al minimo i cambi di contesto, puoi usare alcune delle tecniche già viste in questo argomento.To keep context switches down to a minimum, you can use some of the techniques that we've already seen in this topic. Di seguito sono disponibili alcuni esempi.Let's see some illustrations of doing that. Nel prossimo esempio di pseudocodice viene illustrata la struttura di un gestore eventi che chiama un'API di Windows Runtime per caricare un'immagine, passa a un thread in background per l'elaborazione di tale immagine e quindi torna al thread dell'interfaccia utente per visualizzare l'immagine nell'interfaccia utente.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.
}

In questo scenario, esiste un po' di inefficienza a livello della chiamata di StorageFile::OpenAsync.For this scenario, there's a little bit of ineffiency around the call to StorageFile::OpenAsync. Esiste un cambio di contesto necessario a un thread in background (in modo che il gestore possa restituire l'esecuzione al chiamante) alla ripresa, dopo il quale C++/WinRT ripristina il contesto del thread dell'interfaccia utente.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. In questo caso, però, non è necessario che sia attivo il thread dell'interfaccia utente fino a quando non si sta per aggiornare l'interfaccia utente.But, in this case, it's not necessary to be on the UI thread until we're about to update the UI. Maggiore è il numero di API di Windows Runtime chiamate prima della chiamata a winrt::resume_background, maggiori saranno i cambi di contesto superflui da eseguire.The more Windows Runtime APIs we call before our call to winrt::resume_background, the more unnecessary back-and-forth context switches we incur. La soluzione consiste nel non chiamare alcuna API di Windows Runtime prima della ripresa.The solution is not to call any Windows Runtime APIs before then. Sposta tutte le chiamate dopo 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.
}

Per eseguire operazioni più avanzate, puoi scrivere adapter await personalizzati.If you want to do something more advanced, then you could write your own await adapters. Ad esempio, se vuoi che un co_await riprenda sullo stesso thread su cui viene completata l'azione asincrona (evitando quindi il cambio di contesto), potresti iniziare a scrivere adapter await simili a quelli illustrati di seguito.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.

Nota

L'esempio di codice riportato di seguito viene fornito solo a scopo didattico, in modo da poter iniziare a capire come funzionano gli adapter await.The code example below is provided for educational purposes only; it's to get you started understanding how await adapters work. Se vuoi usare questa tecnica nella tua codebase, ti consigliamo di sviluppare e testare struct per adapter await personalizzati.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). Potresti scrivere ad esempio complete_on_any, complete_on_current e complete_on(dispatcher) .For example, you could write complete_on_any, complete_on_current, and complete_on(dispatcher). Prendi anche in considerazione la possibilità di impostarli come modelli che accettano il tipo IAsyncXxx come parametro modello.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;
};

Per capire come usare gli adapter await no_switch, dovrai prima di tutto tenere presente che quando il compilatore C++ rileva un'espressione co_await cerca funzioni chiamate await_ready, await_suspend e 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. Queste funzioni sono incluse nella libreria C++/WinRT, quindi puoi ottenere un comportamento ragionevole per impostazione predefinita, come indicato di seguito.The C++/WinRT library provides those functions so that you get reasonable behavior by default, like this.

IAsyncAction async{ ProcessFeedAsync() };
co_await async;

Per usare gli adaper await no_switch, cambia semplicemente il tipo dell'espressione co_await da IAsyncXxx a no_switch, come segue.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);

Quindi, invece di cercare tre funzioni await_xxx corrispondenti a IAsyncXxx, il compilatore C++ cerca funzioni corrispondenti a 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.

Informazioni dettagliate sulla funzione winrt::resume_foregroundA deeper dive into winrt::resume_foreground

A partire da C++/WinRT 2.0, la funzione winrt::resume_foreground viene sospesa anche se viene chiamata dal thread del dispatcher. Nelle versioni precedenti può introdurre deadlock in alcuni scenari perché viene sospesa solo se non è già presente nel thread del dispatcher.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).

Il comportamento corrente significa che puoi fare affidamento sulla rimozione dello stack e sulla ripetizione dell'accodamento. Questo aspetto è importante per la stabilità del sistema, soprattutto nel codice di sistemi di ultimo livello.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. L'ultimo listato di codice nella sezione Programmazione basata sull'affinità del thread sopra riportata illustra l'esecuzione di calcoli complessi in un thread in background e quindi il passaggio al thread dell'interfaccia utente appropriato per aggiornare l'interfaccia utente.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).

Viene riportato di seguito l'aspetto interno di 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{ ... };
};

Questo comportamento corrente, confrontato con quello precedente, è analogo alla differenza tra PostMessage e SendMessage nello sviluppo di applicazioni Win32.This current, versus previous, behavior is analogous to the difference between PostMessage and SendMessage in Win32 application development. PostMessage accoda il lavoro e quindi rimuove lo stack senza attendere il completamento del lavoro.PostMessage queues the work and then unwinds the stack without waiting for the work to complete. La rimozione dello stack può essere essenziale.The stack-unwinding can be essential.

Anche la funzione winrt::resume_foreground supporta inizialmente solo la classe CoreDispatcher (associata a CoreWindow), introdotta prima di Windows 10.The winrt::resume_foreground function also initially only supported the CoreDispatcher (tied to a CoreWindow), which was introduced prior to Windows 10. Da allora è stato introdotto un dispatcher più flessibile ed efficiente, ovvero DispatcherQueue.We've since introduced a more flexible and efficient dispatcher: the DispatcherQueue. Puoi creare un oggetto DispatcherQueue per i tuoi scopi.You can create a DispatcherQueue for your own purposes. Esamina questa semplice applicazione console.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();
}

L'esempio precedente crea una coda (contenuta in un controller) in un thread privato e quindi passa il controller alla coroutine.The example above creates a queue (contained within a controller) on a private thread, and then passes the controller to the coroutine. La coroutine può usare la coda per l'attesa (sospensione e ripresa) sul thread privato.The coroutine can use the queue to await (suspend and resume) on the private thread. Un altro uso comune di DispatcherQueue consiste nel creare una coda sul thread dell'interfaccia utente corrente per un'app desktop o Win32 tradizionale.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 };
}

Questo esempio illustra come puoi chiamare e incorporare le funzioni Win32 nei progetti C++/WinRT chiamando semplicemente la funzione CreateDispatcherQueueController di tipo Win32 per creare il controller e quindi trasferire al chiamante la proprietà del controller della coda risultante come oggetto 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. Questo è anche esattamente il modo in cui puoi supportare un accodamento efficiente e senza problemi in un'applicazione desktop Win32 di tipo Petzold esistente.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);
    }
}

Nell'esempio sopra riportato la funzione main semplice inizia creando una finestra.Above, the simple main function begins by creating a window. Come puoi immaginare, questa operazione registra una classe window e chiama CreateWindow per creare la finestra desktop di primo livello.You can imagine that this registers a window class, and calls CreateWindow to create the top-level desktop window. Viene quindi chiamata la funzione CreateDispatcherQueueController per creare il controller della coda prima di chiamare una coroutine con la coda del dispatcher di proprietà del controller.CreateDispatcherQueueController function is then called to create the queue controller before calling some coroutine with the dispatcher queue owned by this controller. Viene quindi immesso un message pump tradizionale in cui la ripresa della coroutine si verifica naturalmente in questo thread.A traditional message pump is then entered where resumption of the coroutine naturally occurs on this thread. A questo punto puoi tornare al mondo elegante delle coroutine per il flusso di lavoro asincrono o basato su messaggi nell'applicazione.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.
}

La chiamata a winrt::resume_foreground eseguirà sempre l'accodamento (queue) e quindi rimuoverà lo stack.The call to winrt::resume_foreground will always queue, and then unwind the stack. Puoi anche facoltativamente impostare la priorità di ripresa.You can also optionally set the resumption priority.

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

In alternativa, puoi usare l'ordine di esecuzione delle query predefinito.Or, using the default queuing order.

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

In alternativa, in questo caso, puoi rilevare l'arresto della coda e gestirlo normalmente.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.
    }
}

L'espressione co_await restituisce true, indicando che la ripresa si verificherà sul thread del dispatcher.The co_await expression returns true, indicating that resumption will occur on the dispatcher thread. In altre parole, l'accodamento ha avuto esito positivo.In other words, that queuing was successful. Al contrario, restituisce false per indicare che l'esecuzione rimane sul thread chiamante perché il controller della coda sta per essere arrestato e non è più in grado di soddisfare le richieste di accodamento.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.

Hai quindi molte possibilità quando combini C++/WinRT con le coroutine, soprattutto nel caso di sviluppo di un'applicazione desktop di tipo Petzold tradizionale.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.

Annullamento di un'operazione asincrona e callback di annullamentoCanceling an asynchronous operation, and cancellation callbacks

Le funzionalità di Windows Runtime per la programmazione asincrona consentono di annullare un'azione o un'operazione asincrona in corso.The Windows Runtime's features for asynchronous programming allow you to cancel an in-flight asynchronous action or operation. Di seguito è riportato un esempio che chiama StorageFolder::GetFilesAsync per recuperare una raccolta di file potenzialmente grande e archivia l'oggetto operazione asincrona risultante in un membro dati.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. L'utente ha la possibilità di annullare l'operazione.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;
};
...

Per illustrare l'implementazione dell'annullamento, iniziamo con un semplice esempio.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();
}

Se esegui l'esempio precedente, ImplicitCancellationAsync stamperà un messaggio al secondo per tre secondi e dopo questo intervallo verrà terminato automaticamente come risultato dell'annullamento.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. Questa procedura funziona perché, in presenza di un'espressione co_await, una coroutine controlla se è stata annullata.This works because, on encountering a co_await expression, a coroutine checks whether it has been cancelled. In caso affermativo, viene generato un corto circuito e in caso contrario viene sospesa normalmente.If it has, then it short-circuits out; and if it hasn't, then it suspends as normal.

L'annullamento può naturalmente verificarsi mentre la coroutine è sospesa.Cancellation can, of course, happen while the coroutine is suspended. Solo alla ripresa o in presenza di un altro co_await, la coroutine controllerà se è stato richiesto l'annullamento.Only when the coroutine resumes, or hits another co_await, will it check for cancellation. Il problema è una latenza potenzialmente con granularità troppo grossolana per la risposta all'annullamento.The issue is one of potentially too-coarse-grained latency in responding to cancellation.

Un'altra opzione prevede quindi il polling esplicito per l'annullamento dall'interno della coroutine.So, another option is to explicitly poll for cancellation from within your coroutine. Aggiorna l'esempio precedente con il codice nel listato seguente.Update the example above with the code in the listing below. In questo nuovo esempio, ExplicitCancellationAsync recupera l'oggetto restituito dalla funzione winrt::get_cancellation_token e lo usa per controllare periodicamente se le coroutine è stata annullata.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. Fino a quando non viene annullata, le coroutine esegue un ciclo infinito. Dopo l'annullamento, il ciclo e la funzione si concludono normalmente.As long as it's not canceled, the coroutine loops indefinitely; once it is canceled, the loop and the function exit normally. Il risultato è lo stesso dell'esempio precedente, ma in questo caso la chiusura avviene in modo esplicito e sotto controllo.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();
}
...

L'attesa di winrt::get_cancellation_token consente di recuperare un token di annullamento con conoscenza dell'azione IAsyncAction che la coroutine genera per tuo conto.Waiting on winrt::get_cancellation_token retrieves a cancellation token with knowledge of the IAsyncAction that the coroutine is producing on your behalf. Puoi usare l'operatore di chiamata di funzione su tale token per richiedere lo stato di annullamento, operazione che corrisponde fondamentalmente al polling per l'annullamento.You can use the function call operator on that token to query the cancellation state—essentially polling for cancellation. Se esegui operazioni associate al calcolo o l'iterazione di una raccolta di grandi dimensioni, questa è una tecnica ragionevole.If you're performing some compute-bound operation, or iterating through a large collection, then this is a reasonable technique.

Registrare un callback di annullamentoRegister a cancellation callback

L'annullamento di Windows Runtime non si propaga automaticamente ad altri oggetti asincroni.The Windows Runtime's cancellation doesn't automatically flow to other asynchronous objects. A partire dalla versione 10.0.17763.0 (Windows 10, versione 1809) di Windows SDK è però possibile registrare un callback di annullamento.But—introduced in version 10.0.17763.0 (Windows 10, version 1809) of the Windows SDK—you can register a cancellation callback. Si tratta di un hook preventivo tramite il quale può essere propagato l'annullamento e che rende possibile l'integrazione con le librerie di concorrenza esistenti.This is a pre-emptive hook by which cancellation can be propagated, and makes it possible to integrate with existing concurrency libraries.

Nel prossimo esempio di codice NestedCoroutineAsync esegue il lavoro, ma non include alcuna logica di annullamento speciale.In this next code example, NestedCoroutineAsync does the work, but it has no special cancellation logic in it. CancellationPropagatorAsync è essenzialmente un wrapper per la coroutine annidata, che inoltra l'annullamento preventivamente.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 registra una funzione lambda per il callback di annullamento e quindi attende (viene sospeso) fino al completamento delle operazioni annidate.CancellationPropagatorAsync registers a lambda function for its own cancellation callback, and then it awaits (it suspends) until the nested work completes. In caso di annullamento di CancellationPropagatorAsync, l'annullamento viene propagato alla coroutine annidata.When or if CancellationPropagatorAsync is canceled, it propagates the cancellation to the nested coroutine. Non è necessario eseguire il polling per l'annullamento e l'annullamento non viene bloccato per un periodo illimitato.There's no need to poll for cancellation; nor is cancellation blocked indefinitely. Questo meccanismo è sufficientemente flessibile per poterlo usare per l'interoperabilità con una coroutine o una libreria di concorrenza senza alcuna conoscenza di 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.

Segnalazione dello statoReporting progress

Se la coroutine restituisce IAsyncActionWithProgress, o IAsyncOperationWithProgress, puoi recuperare l'oggetto restituito dalla funzione winrt::get_progress_token e usarlo per segnalare lo stato a un gestore dello stato.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. Ecco un esempio di codice.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();
}

Nota

Non è corretto implementare più di un gestore di completamento per un'operazione o un'azione asincrona.It's not correct to implement more than one completion handler for an asynchronous action or operation. Puoi avere un solo delegato per l'evento completato oppure puoi usare co_await.You can have either a single delegate for its completed event, or you can co_await it. In presenza di entrambi, il secondo avrà esito negativo.If you have both, then the second will fail. Solo uno dei due tipi di gestori del completamento seguenti è appropriato. Entrambi non sono consentiti per lo stesso oggetto asincrono.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 };

Per altre informazioni sui gestori di completamento, vedi Tipi di delegati per operazioni e azioni asincrone.For more info about completion handlers, see Delegate types for asynchronous actions and operations.

Attivare e poi dimenticareFire and forget

In alcuni casi puoi avere un'attività che può essere eseguita contemporaneamente ad altre operazioni, ma non hai la necessità di attendere il completamento dell'attività (ovvero non ci sono altre operazioni dipendenti) e non serve neanche restituire un valore.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 questo caso puoi attivare l'attività e poi dimenticarla.In that case, you can fire off the task and forget it. Puoi farlo scrivendo una coroutine con tipo restituito winrt::fire_and_forget (invece di uno dei tipi di operazione asincrona di Windows Runtime o concurrency::task).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 è utile anche come tipo restituito del gestore eventi quando devi eseguirvi operazioni asincrone.winrt::fire_and_forget is also useful as the return type of your event handler when you need to perform asynchronous operations in it. Ecco un esempio (vedi anche Riferimenti sicuri e deboli in 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.
}

Il primo argomento (il sender) è stato lasciato senza nome perché non viene mai usato.The first argument (the sender) is left unnamed, because we never use it. Per questo motivo è corretto lasciarlo come riferimento.For that reason we're safe to leave it as a reference. Puoi tuttavia notare che args viene passato per valore.But observe that args is passed by value. Vedi la sezione precedente Passaggio di parametri.See the Parameter-passing section above.

Attesa di un handle del kernelAwaiting a kernel handle

C++/WinRT offre una funzione winrt::resume_on_signal, che è possibile usare per sospendere il processo finché non viene segnalato un evento del kernel.C++/WinRT provides a winrt::resume_on_signal function, which you can use to suspend until a kernel event is signaled. Sei tu ad avere la responsabilità di garantire che l'handle rimanga valido fino a quando co_await resume_on_signal(h) non restituisce il controllo.You're responsible for ensuring that the handle remains valid until your co_await resume_on_signal(h) returns. resume_on_signal non è in grado di eseguire questa operazione, perché potresti aver perso l'handle ancora prima dell'avvio di resume_on_signal, come in questo primo esempio.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.
}

L'HANDLE in ingresso è valido solo fino a quando la funzione non restituisce il controllo e questa funzione, che è una coroutine, restituisce il controllo in corrispondenza del primo punto di sospensione (il primo co_await, in questo caso).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). Durante l'attesa di DoWorkAsync, il controllo è tornato al chiamante, il frame chiamante è uscito dall'ambito e non puoi più sapere se l'handle sarà valido al riavvio della coroutine.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.

Tecnicamente, la coroutine riceve i parametri per valore, come necessario (vedi la sezione precedente Passaggio di parametri).Technically, our coroutine is receiving its parameters by value, as it should (see Parameter-passing above). In questo caso, tuttavia, dobbiamo compiere un passo un più in modo da seguire lo spirito, e non solo la lettera, di tali indicazioni.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). Dobbiamo passare un riferimento sicuro (in altre parole, la proprietà) insieme all'handle.We need to pass a strong reference (in other words, ownership) along with the handle. Ecco come.Here's how.

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

Il passaggio di winrt::handle per valore fornisce una semantica di proprietà in base alla quale hai la garanzia che l'handle del kernel rimanga valido per l'intera durata della coroutine.Passing a winrt::handle by value provides ownership semantics, which ensures that the kernel handle remains valid for the lifetime of the coroutine.

Ecco come puoi chiamare tale 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.
}

È possibile passare un valore di timeout a resume_on_signal, come illustrato in questo esempio.You can pass a timeout value to resume_on_signal, as in this example.

winrt::handle event = ...

if (co_await winrt::resume_on_signal(event.get(), std::literals::2s))
{
    puts("signaled");
}
else
{
    puts("timed out");
}

Semplificazione dei timeout asincroniAsynchronous timeouts made easy

C++/WinRT ha investito in modo significativo sulle coroutine C++.C++/WinRT is invested heavily in C++ coroutines. Il loro effetto sulla scrittura di codice di concorrenza fa la differenza.Their effect on writing concurrency code is transformational. Questa sezione illustra i casi in cui i dettagli dell'asincronia non sono importanti e si vuole solo ottenere il risultato all'istante.This section discusses cases where details of asynchrony are not important, and all you want is the result there and then. Per questo motivo, l'implementazione C++/WinRT dell'interfaccia di operazioni asincrone Windows Runtime IAsyncAction ha una funzione get simile a quella fornita da std::future.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::future.

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

La funzione get effettua un blocco a tempo indeterminato mentre l'oggetto asincrono viene completato.The get function blocks indefinitely, while the async object completes. Gli oggetti asincroni tendono a essere di breve durata, quindi questo è spesso tutto ciò di cui hai bisogno.Async objects tend to be very short-lived, so this is often all you need.

In alcuni casi, tuttavia, questo non è sufficiente e devi abbandonare l'attesa dopo che è trascorso un po' di tempo.But there are cases where that's not sufficient, and you need to abandon the wait after some time has elapsed. La scrittura di tale codice è sempre stata possibile grazie ai blocchi predefiniti forniti da Windows Runtime.Writing that code has always been possible, thanks to the building blocks provided by the Windows Runtime. Ora però C++/WinRT rende molto più semplice questa operazione con la funzione wait_for.But now C++/WinRT makes it a lot easier by providing the wait_for function. Questa funzione è implementata anche in IAsyncAction e, anche in questo caso, è simile a quella fornita da std::future.It's also implementated on IAsyncAction, and again it's similar to that provided by std::future.

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

Nota

wait_for usa std::chrono::duration nell'interfaccia, ma è limitata a un intervallo più piccolo di quanto fornito da std::chrono::duration (circa 49,7 giorni).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).

La funzione wait_for nell'esempio seguente attende circa cinque secondi, quindi controlla il completamento.The wait_for in this next example waits for around five seconds and then it checks completion. Se il confronto ha esito positivo, sai che l'oggetto asincrono è stato completato correttamente.If the comparison is favorable, then you know that the async object completed successfully, and you're done. Se attendi un risultato, puoi semplicemente far seguire a questa procedura una chiamata alla funzione get per recuperare il risultato.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());
    }
}

Poiché per allora l'oggetto asincrono risulterà completato, la funzione get restituisce immediatamente il risultato, senza richiedere un'ulteriore attesa.Because the async object has completed by then, get returns the result immediately, without any further wait. Come puoi notare, wait_for restituisce lo stato dell'oggetto asincrono.As you can see, wait_for returns the state of the async object. Puoi pertanto usare questa funzione per un controllo più granulare, come indicato di seguito.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;
}
  • Tieni presente che AsyncStatus::Completed indica che l'oggetto asincrono è stato completato correttamente e puoi chiamare la funzione get per recuperare eventuali risultati.Remember that AsyncStatus::Completed means that the async object completed successfully, and you may call the get function to retrieve any result.
  • AsyncStatus::Canceled indica che l'oggetto asincrono è stato annullato.AsyncStatus::Canceled means that the async object was canceled. Un annullamento viene in genere richiesto dal chiamante, quindi è raro gestire questo stato.A cancellation is typically requested by the caller, so it would be rare to handle this state. In genere un oggetto asincrono annullato viene semplicemente rimosso.Typically, a cancelled async object is simply discarded.
  • AsyncStatus::Error indica che in qualche modo l'oggetto asincrono ha avuto esito negativo.AsyncStatus::Error means that the async object has failed in some way. Se vuoi, puoi usare la funzione get per generare nuovamente l'eccezione.You can get to rethrow the exception if you wish.
  • AsyncStatus::Started indica che l'oggetto asincrono è ancora in esecuzione.AsyncStatus::Started means that the async object is still running. Il modello asincrono di Windows Runtime non consente né attese multiple, né waiter.The Windows Runtime async pattern doesn't allow multiple waits, nor waiters. Ciò significa che non puoi chiamare wait_for in un ciclo.That means that you can't call wait_for in a loop. Se si è verificato il timeout dell'attesa, le scelte disponibili sono poche.If the wait has effectively timed-out, then you're left with a few choices. Può abbandonare l'oggetto oppure eseguire il polling dello stato prima di chiamare get per recuperare i risultati.You can abandon the object, or you can poll its status before calling get to retrieve any result. È tuttavia preferibile rimuovere l'oggetto a questo punto.But it's best just to discard the object at this point.

Restituzione di una matrice in modo asincronoReturning an array asynchronously

Di seguito è riportato un esempio di MIDL 3.0 che genera l'errore MIDL2025: [msg]syntax error [context]: expecting > or, near "[" (errore di sintassi: previsto > o , accanto a "").Below is an example of MIDL 3.0 that produces error MIDL2025: [msg]syntax error [context]: expecting > or, near "[".

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

L'errore è dovuto al fatto che non è possibile usare una matrice come argomento di tipo parametro per un'interfaccia con parametri.The reason is that it's invalid to use an array as a parameter type argument to a parameterized interface. È quindi necessario un modo meno ovvio per riuscire a passare in modo asincrono una matrice da un metodo della classe di runtime.So we need a less obvious way to achieve the aim of asynchronously passing an array back from a runtime class method.

Puoi restituire la matrice sottoposta a conversione boxing in un oggetto PropertyValue.You can return the array boxed into a PropertyValue object. Il codice chiamante ne eseguirà quindi l'unboxing.The calling code then unboxes it. Di seguito è riportato un esempio di codice che puoi provare a usare aggiungendo la classe di runtime SampleComponent a un progetto Windows Runtime Component (C++/WinRT) e quindi utilizzando tale classe da un altro progetto, ad esempio Core App (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.
...

API importantiImportant APIs