Concorrenza e operazioni asincrone con C++/WinRTConcurrency and asynchronous operations with C++/WinRT

Importante

Questo argomento presenta i concetti di coroutine e co_await, che consigliamo di usare nelle applicazioni dell'interfaccia utente e in quelle non di interfaccia utente.This topic introduces the concepts of coroutines and co_await, which we recommend that you use in both your UI and in your non-UI applications. Per semplicità, la maggior parte degli esempi di codice forniti in questo argomento introduttivo illustra progetti di applicazione console di Windows (C++/WinRT) .For simplicity, most of the code examples in this introductory topic show Windows Console Application (C++/WinRT) projects. Gli ultimi esempi di codice di questo argomento usano le coroutine, ma per praticità gli esempi di applicazione console continuano anche a usare la chiamata della funzione get di blocco appena prima di uscire, in modo che l'applicazione non venga chiusa prima di terminare la stampa dell'output.The later code examples in this topic do use coroutines, but for convenience the console application examples also continue to use the blocking get function call just before exiting, so that the application doesn't exit before finishing printing its output. Non devi eseguire questa operazione (chiamata della funzione get di blocco) da un thread dell'interfaccia utente.You won't do that (call the blocking get function) from a UI thread. Devi invece usare l'istruzione co_await.Instead, you'll use the co_await statement. Le tecniche da usare nelle applicazioni dell'interfaccia utente sono descritte nell'argomento Concorrenza e asincronia più avanzate.The techniques that you'll use in your UI applications are described in the topic More advanced concurrency and asynchrony.

Questo argomento introduttivo illustra alcuni dei modi per creare e utilizzare oggetti asincroni di Windows Runtime con C++/WinRT.This introductory topic shows some of the ways in which you can both create and consume Windows Runtime asynchronous objects with C++/WinRT. Dopo la lettura di questo argomento, in particolare per le tecniche da usare nelle applicazioni dell'interfaccia utente, vedi anche Concorrenza e asincronia più avanzate.After reading this topic, especially for techniques you'll use in your UI applications, also see More advanced concurrency and asynchrony.

Operazioni asincrone e funzioni "Async" di Windows RuntimeAsynchronous operations and Windows Runtime "Async" functions

Qualsiasi API Windows Runtime per il cui completamento possono essere richiesti più di 50 millisecondi viene implementata come funzione asincrona (con un nome che termina con "Async").Any Windows Runtime API that has the potential to take more than 50 milliseconds to complete is implemented as an asynchronous function (with a name ending in "Async"). L'implementazione di una funzione asincrona avvia il lavoro su un altro thread e restituisce immediatamente un oggetto che rappresenta l'operazione asincrona.The implementation of an asynchronous function initiates the work on another thread, and returns immediately with an object that represents the asynchronous operation. Al termine dell'operazione asincrona, l'oggetto restituito contiene qualsiasi valore risultante dal lavoro.When the asynchronous operation completes, that returned object contains any value that resulted from the work. Lo spazio dei nomi Windows Runtime Windows::Foundation contiene quattro tipi di oggetti operazione asincrona.The Windows::Foundation Windows Runtime namespace contains four types of asynchronous operation object.

Ognuno di questi tipi di operazione asincrona viene proiettato in un tipo corrispondente nello spazio dei nomi C++/WinRT winrt::Windows::Foundation.Each of these asynchronous operation types is projected into a corresponding type in the winrt::Windows::Foundation C++/WinRT namespace. C++/WinRT contiene anche uno struct adapter await interno.C++/WinRT also contains an internal await adapter struct. Non viene usato direttamente, ma grazie a tale struct, puoi scrivere un'istruzione co_await per attendere in modo cooperativo il risultato di qualsiasi funzione che restituisce uno di questi tipi di operazione asincrona.You don't use it directly but, thanks to that struct, you can write a co_await statement to cooperatively await the result of any function that returns one of these asynchronous operation types. Puoi anche creare coroutine personali che restituiscono questi tipi.And you can author your own coroutines that return these types.

Un esempio di funzione asincrona di Windows è SyndicationClient::RetrieveFeedAsync, che restituisce un oggetto operazione asincrona di tipo IAsyncOperationWithProgress<TResult, TProgress> .An example of an asynchronous Windows function is SyndicationClient::RetrieveFeedAsync, which returns an asynchronous operation object of type IAsyncOperationWithProgress<TResult, TProgress>.

Esaminiamo alcuni modi (prima con blocco e poi senza blocco) per usare C++/WinRT per chiamare un'API come questa.Let's look at some ways—first blocking, and then non-blocking—of using C++/WinRT to call an API such as that. Solo per illustrare le idee di base, nei prossimi esempi di codice useremo un progetto di applicazione console di Windows (C++/WinRT) .Just for illustration of the basic ideas, we'll be using a Windows Console Application (C++/WinRT) project in the next few code examples. Le tecniche più appropriate per un'applicazione dell'interfaccia utente sono descritte in Concorrenza e asincronia più avanzate.Techniques that are more appropriate for a UI application are discussed in More advanced concurrency and asynchrony.

Bloccare il thread chiamanteBlock the calling thread

L'esempio di codice seguente riceve un oggetto operazione asincrona da RetrieveFeedAsync e chiama get su tale oggetto per bloccare il thread chiamante fino a quando non sono disponibili i risultati dell'operazione asincrona.The code example below receives an asynchronous operation object from RetrieveFeedAsync, and it calls get on that object to block the calling thread until the results of the asynchronous operation are available.

Se vuoi copiare e incollare questo esempio direttamente nel file di codice sorgente principale di un progetto Applicazione console Windows (C++/WinRT) , imposta prima di tutto Senza intestazioni precompilate nelle proprietà del progetto.If you want to copy-paste this example directly into the main source code file of a Windows Console Application (C++/WinRT) project, then first set Not Using Precompiled Headers in project properties.

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

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;

void ProcessFeed()
{
    Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
    SyndicationClient syndicationClient;
    SyndicationFeed syndicationFeed{ syndicationClient.RetrieveFeedAsync(rssFeedUri).get() };
    // use syndicationFeed.
}

int main()
{
    winrt::init_apartment();
    ProcessFeed();
}

La chiamata a get semplifica la scrittura del codice ed è ideale per app console e thread in background nei casi in cui non vuoi usare una coroutine per qualsiasi motivo.Calling get makes for convenient coding, and it's ideal for console apps or background threads where you may not want to use a coroutine for whatever reason. Dato che non è una chiamata asincrona né simultanea, non è appropriata per un thread dell'interfaccia utente. Se tenti di usarne una, verrà generata un'asserzione nelle build non ottimizzate.But it's not concurrent nor asynchronous, so it's not appropriate for a UI thread (and an assertion will fire in unoptimized builds if you attempt to use it on one). Per evitare ritardi nell'esecuzione di altre utili attività da parte dei thread del sistema operativo, è necessario usare una tecnica diversa.To avoid holding up OS threads from doing other useful work, we need a different technique.

Scrivere una coroutineWrite a coroutine

C++/WinRT integra le coroutine C++ nel modello di programmazione per fornire un modo naturale per attendere un risultato in modo cooperativo.C++/WinRT integrates C++ coroutines into the programming model to provide a natural way to cooperatively wait for a result. Puoi creare un'operazione asincrona Windows Runtime personalizzata scrivendo una coroutine.You can produce your own Windows Runtime asynchronous operation by writing a coroutine. Nell'esempio di codice seguente ProcessFeedAsync è la coroutine.In the code example below, ProcessFeedAsync is the coroutine.

Nota

La funzione get esiste nel tipo di proiezione C++/WinRT winrt::Windows::Foundation::IAsyncAction, quindi puoi chiamare la funzione da qualsiasi progetto C++/WinRT.The get function exists on the C++/WinRT projection type winrt::Windows::Foundation::IAsyncAction, so you can call the function from within any C++/WinRT project. Non troverai la funzione elencata come membro dell'interfaccia IAsyncAction, perché get non fa parte della superficie dell'interfaccia ABI (Application Binary Interface) del tipo effettivo di Windows Runtime IAsyncAction.You will not find the function listed as a member of the IAsyncAction interface, because get is not part of the application binary interface (ABI) surface of the actual Windows Runtime type IAsyncAction.

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

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;

void PrintFeed(SyndicationFeed const& syndicationFeed)
{
    for (SyndicationItem const& syndicationItem : syndicationFeed.Items())
    {
        std::wcout << syndicationItem.Title().Text().c_str() << std::endl;
    }
}

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

int main()
{
    winrt::init_apartment();

    auto processOp{ ProcessFeedAsync() };
    // do other work while the feed is being printed.
    processOp.get(); // no more work to do; call get() so that we see the printout before the application exits.
}

Una coroutine è una funzione che può essere sospesa e ripresa.A coroutine is a function that can be suspended and resumed. Nella precedente coroutine ProcessFeedAsync, quando l'istruzione co_await viene raggiunta, la coroutine esegue la chiamata a RetrieveFeedAsync in modo asincrono e quindi sospende immediatamente se stessa e restituisce il controllo al chiamante (main nell'esempio precedente).In the ProcessFeedAsync coroutine above, when the co_await statement is reached, the coroutine asynchronously initiates the RetrieveFeedAsync call and then it immediately suspends itself and returns control back to the caller (which is main in the example above). main può quindi continuare a lavorare mentre il feed viene recuperato e stampato.main can then continue to do work while the feed is being retrieved and printed. Al termine, ovvero al completamento della chiamata a RetrieveFeedAsync, la coroutine ProcessFeedAsync riprende in corrispondenza dell'istruzione successiva.When that's done (when the RetrieveFeedAsync call completes), the ProcessFeedAsync coroutine resumes at the next statement.

Puoi aggregare una coroutine in altre coroutine,You can aggregate a coroutine into other coroutines. puoi chiamare get per applicare il blocco e attenderne il completamento, per ottenere il risultato, se disponibile,Or you can call get to block and wait for it to complete (and get the result if there is one). oppure puoi passare a un altro linguaggio di programmazione che supporta Windows Runtime.Or you can pass it to another programming language that supports the Windows Runtime.

È anche possibile gestire gli eventi completati e/o in corso di operazioni e azioni asincrone tramite i delegati.It's also possible to handle the completed and/or progress events of asynchronous actions and operations by using delegates. Per informazioni dettagliate ed esempi di codice, vedi Tipi di delegati per operazioni e azioni asincrone.For details, and code examples, see Delegate types for asynchronous actions and operations.

Come puoi notare, nell'esempio di codice precedente continuiamo a usare la chiamata della funzione get di blocco appena prima di uscire da main.As you can see, in the code example above, we continue to use the blocking get function call just before exiting main. Lo scopo è impedire che l'applicazione venga chiusa prima di terminare la stampa dell'output.But that's only so that the application doesn't exit before finishing printing its output.

Restituire un tipo Windows Runtime in modo asincronoAsynchronously return a Windows Runtime type

Nell'esempio seguente viene eseguito il wrapping di una chiamata a RetrieveFeedAsync per un URI specifico, per ottenere una funzione RetrieveBlogFeedAsync che restituisce SyndicationFeed in modo asincrono.In this next example we wrap a call to RetrieveFeedAsync, for a specific URI, to give us a RetrieveBlogFeedAsync function that asynchronously returns a SyndicationFeed.

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

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;

void PrintFeed(SyndicationFeed const& syndicationFeed)
{
    for (SyndicationItem const& syndicationItem : syndicationFeed.Items())
    {
        std::wcout << syndicationItem.Title().Text().c_str() << std::endl;
    }
}

IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> RetrieveBlogFeedAsync()
{
    Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
    SyndicationClient syndicationClient;
    return syndicationClient.RetrieveFeedAsync(rssFeedUri);
}

int main()
{
    winrt::init_apartment();

    auto feedOp{ RetrieveBlogFeedAsync() };
    // do other work.
    PrintFeed(feedOp.get());
}

Nell'esempio precedente RetrieveBlogFeedAsync restituisce un oggetto IAsyncOperationWithProgress, che contiene entrambi i valori progress e return.In the example above, RetrieveBlogFeedAsync returns an IAsyncOperationWithProgress, which has both progress and a return value. È possibile eseguire altre attività mentre la funzione RetrieveBlogFeedAsync viene eseguita e recupera il feed.We can do other work while RetrieveBlogFeedAsync is doing its thing and retrieving the feed. Chiamiamo quindi get su tale oggetto operazione asincrona per applicare il blocco, attendere il completamento e quindi ottenere i risultati dell'operazione.Then, we call get on that asynchronous operation object to block, wait for it to complete, and then obtain the results of the operation.

Se intendi restituire in modo asincrono un tipo Windows Runtime, devi restituire un oggetto IAsyncOperation<TResult> o IAsyncOperationWithProgress<TResult, TProgress> .If you're asynchronously returning a Windows Runtime type, then you should return an IAsyncOperation<TResult> or an IAsyncOperationWithProgress<TResult, TProgress>. Risultano appropriati qualsiasi classe di runtime di prima o terza parte oppure qualsiasi tipo che può essere passato verso o da una funzione di Windows Runtime (ad esempio, int o winrt::hstring).Any first- or third-party runtime class qualifies, or any type that can be passed to or from a Windows Runtime function (for example, int, or winrt::hstring). Il compilatore ti sarà di aiuto con un errore che indica che "deve essere di tipo WinRT" se provi a usare uno di questi tipi di operazione asincrona con un tipo non Windows Runtime.The compiler will help you with a "must be WinRT type" error if you try to use one of these asynchronous operation types with a non-Windows Runtime type.

Se una coroutine non include almeno un'istruzione co_await, per essere qualificata come coroutine deve avere almeno un'istruzione co_returno co_yield.If a coroutine doesn't have at least one co_await statement then, in order to qualify as a coroutine, it must have at least one co_return or one co_yield statement. In alcuni casi la coroutine potrà restituire un valore senza introdurre un'operazione asincrona e dunque senza bloccare né cambiare contesto.There will be cases where your coroutine can return a value without introducing any asynchrony, and therefore without blocking nor switching context. Di seguito è riportato un esempio in cui ciò avviene (la seconda volta e le successive in cui viene chiamata) tramite la memorizzazione di un valore nella cache.Here's an example that does that (the second and subsequent times it's called) by caching a value.

winrt::hstring m_cache;

IAsyncOperation<winrt::hstring> ReadAsync()
{
    if (m_cache.empty())
    {
        // Asynchronously download and cache the string.
    }
    co_return m_cache;
}

Restituire un tipo non Windows Runtime in modo asincronoAsynchronously return a non-Windows-Runtime type

Se intendi restituire in modo asincrono un tipo non Windows Runtime, devi restituire una classe concurrency::task PPL (Parallel Patterns Library).If you're asynchronously returning a type that's not a Windows Runtime type, then you should return a Parallel Patterns Library (PPL) concurrency::task. È consigliabile usare concurrency::task perché consente prestazioni migliori (e migliore compatibilità futura) rispetto a std::future.We recommend concurrency::task because it gives you better performance (and better compatibility going forward) than std::future does.

Suggerimento

Se includi <pplawait.h>, potrai usare concurrency::task come tipo di coroutine.If you include <pplawait.h>, then you can use concurrency::task as a coroutine type.

// main.cpp
#include <iostream>
#include <ppltasks.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;

concurrency::task<std::wstring> RetrieveFirstTitleAsync()
{
    return concurrency::create_task([]
        {
            Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
            SyndicationClient syndicationClient;
            SyndicationFeed syndicationFeed{ syndicationClient.RetrieveFeedAsync(rssFeedUri).get() };
            return std::wstring{ syndicationFeed.Items().GetAt(0).Title().Text() };
        });
}

int main()
{
    winrt::init_apartment();

    auto firstTitleOp{ RetrieveFirstTitleAsync() };
    // Do other work here.
    std::wcout << firstTitleOp.get() << std::endl;
}

Passaggio di parametriParameter-passing

Per le funzioni sincrone è consigliabile usare i parametri const& per impostazione predefinita.For synchronous functions, you should use const& parameters by default. Ciò eviterà l'overhead delle copie, che implica il conteggio dei riferimenti, con conseguenti aumenti e diminuzioni interlocked.That will avoid the overhead of copies (which involve reference counting, and that means interlocked increments and decrements).

// Synchronous function.
void DoWork(Param const& value);

Possono tuttavia verificarsi problemi se passi un parametro di riferimento a una coroutine.But you can run into problems if you pass a reference parameter to a coroutine.

// NOT the recommended way to pass a value to a coroutine!
IASyncAction DoWorkAsync(Param const& value)
{
    // While it's ok to access value here...

    co_await DoOtherWorkAsync(); // (this is the first suspension point)...

    // ...accessing value here carries no guarantees of safety.
}

In una coroutine l'esecuzione è sincrona fino al primo punto di sospensione, dove il controllo viene restituito al chiamante e il frame chiamante esce dall'ambito.In a coroutine, execution is synchronous up until the first suspension point, where control is returned to the caller and the calling frame goes out of scope. Alla ripresa della coroutine potrebbe essere successo qualsiasi cosa al valore di origine a cui fa riferimento un parametro di riferimento.By the time the coroutine resumes, anything might have happened to the source value that a reference parameter references. Dal punto di vista della coroutine, un parametro di riferimento ha una durata non controllata.From the coroutine's perspective, a reference parameter has uncontrolled lifetime. Nell'esempio precedente possiamo accedere in sicurezza a value fino a co_await, ma non dopo.So, in the example above, we're safe to access value up until the co_await, but not after it. Nel caso in cui value venga distrutto dal chiamante, il tentativo di accedervi all'interno della coroutine dopo la distruzione comporta un danneggiamento della memoria.In the event that value is destructed by the caller, attempting to access it inside the coroutine after that results in a memory corruption. Allo stesso modo, possiamo passare in modo sicuro value a DoOtherWorkAsync qualora ci sia la possibilità che la funzione venga sospesa e quindi provare a usare value alla ripresa.Nor can we safely pass value to DoOtherWorkAsync if there's any risk that that function will in turn suspend and then try to use value after it resumes.

Per fare in modo che i parametri siano utilizzabili in modo sicuro dopo la sospensione e la ripresa, le coroutine devono usare pass-by-value per impostazione predefinita, al fine di garantire che eseguano l'acquisizione in base al valore ed evitare problemi di durata.To make parameters safe to use after suspending and resuming, your coroutines should use pass-by-value by default to ensure that they capture by value, and avoid lifetime issues. Sono rari i casi in cui puoi evitare di seguire queste indicazioni con la certezza che non comporti problemi di sicurezza.Cases when you can deviate from that guidance because you're certain that it's safe to do so are going to be rare.

// Coroutine
IASyncAction DoWorkAsync(Param value); // not const&

Affinché venga passato per valore, l'argomento non deve essere dispendioso da spostare o copiare. Questo è in genere il caso di un puntatore intelligente.Passing by value requires that the argument be inexpensive to move or copy; and that's typically the case for a smart pointer.

È anche discutibile che sia buona norma passare il valore const (a meno che non si voglia spostarlo).It's also arguable that (unless you want to move the value) passing by const value is good practice. Ciò non avrà alcun effetto sul valore di origine da cui effettui una copia, ma rende chiara l'intenzione e risulta utile in caso di modifica accidentale della copia.It won't have any effect on the source value from which you're making a copy, but it makes the intent clear, and helps if you inadvertently modify the copy.

// coroutine with strictly unnecessary const (but arguably good practice).
IASyncAction DoWorkAsync(Param const value);

Vedi anche Vettori e matrici standard, in cui viene descritto come passare un vettore standard in un computer chiamato asincrono.Also see Standard arrays and vectors, which deals with how to pass a standard vector into an asynchronous callee.

Se non riesci a modificare la firma della coroutine, ma riesci a modificare l'implementazione, puoi creare una copia locale prima della prima istanza di co_await.If you can't change your coroutine's signature, but you can change the implementation, then you can make a local copy before the first co_await.

IASyncAction DoWorkAsync(Param const& value)
{
    auto safe_value = value;
    // It's ok to access both safe_value and value here.

    co_await DoOtherWorkAsync();

    // It's ok to access only safe_value here (not value).
}

Se Param è dispendioso da copiare, estrai solo le parti necessarie prima della prima istanza di co_await.If Param is expensive to copy, then extract just the pieces you need before the first co_await.

IASyncAction DoWorkAsync(Param const& value)
{
    auto safe_data = value.data;
    // It's ok to access safe_data, value.data, and value here.

    co_await DoOtherWorkAsync();

    // It's ok to access only safe_data here (not value.data, nor value).
}

Accesso sicuro al puntatore this in una coroutine membro di classeSafely accessing the this pointer in a class-member coroutine

Vedi Riferimenti sicuri e deboli in C++/WinRT.See Strong and weak references in C++/WinRT.

API importantiImportant APIs