Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Importante
Questo argomento presenta i concetti delle coroutine e co_await, che è consigliabile usare sia nell'interfaccia utente che nelle applicazioni non dell'interfaccia utente. Per semplicità, la maggior parte degli esempi di codice in questo argomento introduttivo mostra progetti Applicazione console di Windows (C++/WinRT). Gli esempi di codice riportati più avanti in questo argomento utilizzano le coroutine, ma per comodità gli esempi di applicazioni da console continuano a usare la chiamata di funzione bloccante get subito prima della chiusura, in modo che l'applicazione non si chiuda prima di aver terminato la stampa dell'output. Non lo farai (chiamare la funzione bloccante get) nel thread dell'interfaccia utente. Si userà invece l'istruzione co_await . Le tecniche che verranno usate nelle applicazioni dell'interfaccia utente sono descritte nell'argomento Concorrenza avanzata e asincronia.
Questo argomento introduttivo illustra alcuni dei modi in cui è possibile creare e utilizzare Windows Runtime oggetti asincroni con C++/WinRT. Dopo aver letto questo argomento, in particolare per le tecniche che verranno usate nelle applicazioni dell'interfaccia utente, vedere Anche Concorrenza avanzata e asincronia.
Operazioni asincrone e funzioni "Async" di Windows Runtime
Qualsiasi API Windows Runtime che può richiedere più di 50 millisecondi per il completamento viene implementata come funzione asincrona (con un nome che termina con "Async"). L'implementazione di una funzione asincrona avvia il lavoro su un altro thread e restituisce immediatamente con un oggetto che rappresenta l'operazione asincrona. Al termine dell'operazione asincrona, l'oggetto restituito contiene qualsiasi valore risultante dal lavoro. Lo spazio dei nomi Windows::Foundation di Windows Runtime contiene quattro tipi di oggetti di operazione asincrona.
- IAsyncAction,
- IAsyncActionWithProgress<TProgress>,
- IAsyncOperation<TResult> e
- IAsyncOperationWithProgress<TResult, TProgress>.
Ognuno di questi tipi di operazione asincrona viene proiettato in un tipo corrispondente nello spazio dei nomi winrt::Windows::Foundation C++/WinRT. C++/WinRT contiene anche una struct interna di adattamento per await. Non viene usato direttamente, ma, grazie a tale struct, è possibile scrivere un'istruzione co_await per attendere in modo cooperativo il risultato di qualsiasi funzione che restituisca uno di questi tipi di operazione asincrona. Ed è possibile creare coroutine personalizzate che restituiscono questi tipi.
Un esempio di funzione asincrona Windows è SyndicationClient::RetrieveFeedAsync, che restituisce un oggetto operazione asincrona di tipo IAsyncOperationWithProgress<TResult, TProgress>.
Esaminiamo alcuni modi, prima bloccando e poi non bloccando, l'uso di C++/WinRT per chiamare un'API come questa. Solo per illustrare le idee di base, useremo un progetto di applicazione console di Windows (C++/WinRT) nei prossimi esempi di codice. Le tecniche più appropriate per un'applicazione dell'interfaccia utente sono descritte in Concorrenza avanzata e asincronia.
Bloccare il thread chiamante
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.
Se vuoi copiare e incollare direttamente questo esempio nel file di codice sorgente principale di un progetto Applicazione console di Windows (C++/WinRT), imposta prima Not Using Precompiled Headers nelle proprietà del progetto.
// 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();
}
Chiamare get rende il codice più pratico da scrivere ed è ideale per le applicazioni console o per i thread in esecuzione in background, nei casi in cui per qualche motivo non si desideri usare una coroutine. Ma non è simultaneo né asincrono, quindi non è appropriato per un thread dell'interfaccia utente (e un'asserzione verrà attivata in compilazioni non ottimizzate se si tenta di usarlo su uno). Per evitare che i thread del sistema operativo eseseguono altre operazioni utili, è necessaria una tecnica diversa.
Scrivere una coroutine
C++/WinRT integra le coroutine C++ nel modello di programmazione per offrire un modo naturale per attendere in modo cooperativo un risultato. È possibile creare una propria operazione asincrona di Windows Runtime scrivendo una coroutine. Nell'esempio di codice seguente, ProcessFeedAsync è la coroutine.
Note
La funzione get esiste nel tipo di proiezione C++/WinRT winrt::Windows::Foundation::IAsyncAction, in modo da poter chiamare la funzione da qualsiasi progetto C++/WinRT. La funzione non è elencata come membro dell'interfaccia IAsyncAction, perché get non fa parte della superficie dell'interfaccia ABI (Application Binary Interface) del tipo IAsyncAction Windows Runtime effettivo.
// 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. Nella coroutine ProcessFeedAsync precedente, quando viene raggiunta l'istruzione co_await , la coroutine avvia in modo asincrono la chiamata RetrieveFeedAsync e quindi sospende immediatamente se stessa e restituisce il controllo al chiamante (che è principale nell'esempio precedente).
main può quindi continuare a lavorare mentre il feed viene recuperato e stampato. Una volta completata l'operazione (quando termina la chiamata RetrieveFeedAsync), la coroutine ProcessFeedAsync riprende dall'istruzione successiva.
È possibile aggregare una coroutine in altre coroutine. In alternativa, è possibile chiamare get per bloccare e attendere il completamento (e ottenere il risultato se presente). In alternativa, è possibile passarlo a un altro linguaggio di programmazione che supporta il Windows Runtime.
È anche possibile gestire gli eventi di completamento e/o di avanzamento di azioni e operazioni asincrone mediante l'uso di delegati. Per informazioni dettagliate ed esempi di codice, vedere Delegare i tipi per azioni e operazioni asincrone.
Come si può vedere, nell'esempio di codice qui sopra continuiamo a usare la chiamata bloccante alla funzione get subito prima di uscire da main. Ma questo è solo in modo che l'applicazione non venga chiusa prima di terminare la stampa dell'output.
Restituire in modo asincrono un tipo di Windows Runtime
In questo esempio seguente viene eseguito il wrapping di una chiamata a RetrieveFeedAsync, per un URI specifico, per fornire una funzione RetrieveBlogFeedAsync che restituisce in modo asincrono un 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 ha sia lo stato di avanzamento che un valore restituito. È possibile eseguire altre operazioni mentre RetrieveBlogFeedAsync esegue le operazioni e recupera il feed. Si chiama quindi get sull'oggetto dell'operazione asincrona per bloccare l'esecuzione, attenderne il completamento e poi ottenere i risultati dell'operazione.
Se si restituisce asincronamente un tipo di Windows Runtime, è necessario restituire un IAsyncOperation<TResult> o un IAsyncOperationWithProgress<TResult, TProgress>. Qualsiasi classe runtime proprietaria o di terze parti è idonea, così come qualsiasi tipo che può essere passato a o restituito da una funzione di Windows Runtime, ad esempio int o winrt::hstring. Il compilatore segnalerà l'errore "T deve essere di tipo WinRT" se si tenta di usare uno di questi tipi di operazione asincrona con un tipo che non è di Windows Runtime.
Se una coroutine non contiene almeno un'istruzione co_await, per essere considerata una coroutine deve contenere almeno un'istruzione co_return o co_yield. Ci saranno casi in cui la coroutine può restituire un valore senza introdurre asincronia e quindi senza bloccare né cambiare contesto. Ecco un esempio che esegue questa operazione (la seconda e successiva volta che viene chiamata) memorizzando nella cache un valore.
winrt::hstring m_cache;
IAsyncOperation<winrt::hstring> ReadAsync()
{
if (m_cache.empty())
{
// Asynchronously download and cache the string.
}
co_return m_cache;
}
Restituire in modo asincrono un tipo non-Windows Runtime
Se si restituisce in modo asincrono un tipo che non è un tipo Windows Runtime, è necessario restituire una libreria PPL (Parallel Patterns Library) concurrency::task. È consigliabile usare concurrency::task perché offre prestazioni migliori (e maggiore compatibilità in futuro) rispetto a std::future .
Suggerimento
Se si include <pplawait.h>, è possibile usare concurrency::task come tipo coroutine.
// 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 parametri
Per le funzioni sincrone, dovresti usare i parametri const& per impostazione predefinita. In questo modo si evita il sovraccarico delle copie (che comportano il conteggio dei riferimenti e ciò significa incrementi e decrementi interlocked).
// Synchronous function.
void DoWork(Param const& value);
Tuttavia, si possono verificare problemi se si passa un parametro di riferimento a una 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, nel quale il controllo viene restituito al chiamante e lo stack frame del chiamante esce di ambito. Al momento della ripresa della coroutine, è possibile che tutto sia successo al valore di origine a cui fa riferimento un parametro di riferimento. Dal punto di vista della coroutine, un parametro di riferimento ha una durata non controllata. Quindi, nell'esempio precedente, è possibile accedere al valore fino a , co_awaitma non dopo. Nel caso in cui il valore venga distrutto dal chiamante, il tentativo di accedervi all'interno della coroutine dopo questo comporta un danneggiamento della memoria. Né è possibile passare in modo sicuro il valore a DoOtherWorkAsync se esiste un rischio che tale funzione sospende a sua volta e quindi tenta di usare il valore dopo la ripresa.
Per fare in modo che i parametri siano sicuri da utilizzare dopo la sospensione e la successiva ripresa, le coroutine dovrebbero usare per impostazione predefinita il passaggio per valore, così da garantire che li acquisiscano per valore ed evitare problemi di durata degli oggetti. I casi in cui è possibile discostarsi da tali indicazioni perché si è certi che farlo sia sicuro saranno rari.
// Coroutine
IASyncAction DoWorkAsync(Param value); // not const&
Il passaggio per valore richiede che lo spostamento o la copia dell'argomento siano poco costosi; e questo è in genere il caso di un puntatore intelligente.
Si può anche sostenere che, a meno che non si voglia spostare il valore, il passaggio per valore costante sia una buona pratica. Non avrà alcun effetto sul valore sorgente da cui si sta copiando, ma rende chiara l’intenzione e aiuta nel caso in cui si modifichi accidentalmente la copia.
// coroutine with strictly unnecessary const (but arguably good practice).
IASyncAction DoWorkAsync(Param const value);
Vedere anche Matrici e vettori standard, che illustrano come passare un vettore standard in un chiamato asincrono.
Se non è possibile modificare la firma della coroutine, ma è possibile modificare l'implementazione, è possibile creare una copia locale prima del primo 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 è costoso da copiare, estrarre solo i pezzi necessari prima del primo 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 una classe
Vedi Riferimenti sicuri e deboli in C++/WinRT.
API importanti
- classe concurrency::task
- Interfaccia IAsyncAction
- Interfaccia IAsyncActionWithProgress<TProgress>
- Interfaccia IAsyncOperation<TResult>
- Interfaccia IAsyncOperationWithProgress<TResult, TProgress>
- Metodo SyndicationClient::RetrieveFeedAsync
- classe SyndicationFeed