Asincronia e interoperabilità tra C++/WinRT e C++/CXAsynchrony, and interop between C++/WinRT and C++/CX

Suggerimento

Anche se è consigliabile leggere questo argomento dall'inizio, è possibile passare direttamente a un riepilogo delle tecniche di interoperabilità nella sezione Panoramica della conversione dal codice C++/CX asincrono a C++/WinRT.Although we recommend that you read this topic from the beginning, you can jump straight to a summary of interop techniques in the Overview of porting C++/CX async to C++/WinRT section.

Si tratta di un argomento avanzato relativo alla conversione graduale da C++/CX a C++/WinRT.This is an advanced topic related to gradually porting to C++/WinRT from C++/CX. Questo argomento prosegue la descrizione iniziata nell'argomento Interoperabilità tra C++/WinRT e C++/CX.This topic picks up where the topic Interop between C++/WinRT and C++/CX leaves off.

Se la dimensione o la complessità della base di codici rende necessario convertire gradualmente il progetto, è necessario un processo di conversione in cui per un certo periodo i codici C++/CX e C++/WinRT coesistono nello stesso progetto.If the size or complexity of your codebase makes it necessary to port your project gradually, then you'll need a porting process in which for a time C++/CX and C++/WinRT code exists side by side in the same project. Se si dispone di codice asincrono, potrebbe essere necessario che le catene di attività della libreria PPL (Parallel Patterns Library) e le coroutine coesistano nel progetto man mano che si converte gradualmente il codice sorgente.If you have asynchronous code, then you might need to have Parallel Patterns Library (PPL) task chains and coroutines exist side by side in your project as you gradually port your source code. Questo argomento è incentrato sulle tecniche per l'interoperabilità tra i codici asincroni C++/CX e C++/WinRT.This topic focuses on techniques for interoperating between asynchronous C++/CX code and asynchronous C++/WinRT code. Queste tecniche possono essere usate singolarmente o insieme.You can use these techniques individually, or together. Le tecniche consentono di apportare modifiche locali graduali e controllate lungo il percorso di conversione del progetto, senza che ogni modifica si propaghi in modo incontrollato in tutto il progetto.The techniques allow you to make gradual, controlled, local changes along the path toward porting your entire project, without having each change cascade uncontrollably throughout the project.

Prima di leggere questo argomento, è consigliabile vedere Interoperabilità tra C++/WinRT e C++/CX.Before reading this topic, it's a good idea to read Interop between C++/WinRT and C++/CX. Questo argomento illustra infatti come preparare il progetto per una conversione graduale.That topic shows you how to prepare your project for gradual porting. Introduce inoltre due funzioni helper che è possibile usare per convertire un oggetto C++/CX in un oggetto C++/WinRT (e viceversa).It also introduces two helper functions that you can use to convert a C++/CX object into a C++/WinRT object (and vice versa). Questo argomento sulla modalità asincrona si basa su tali informazioni e usa le funzioni helper.This topic about asynchrony builds on that info, and it uses those helper functions.

Nota

Esistono alcune limitazioni alla conversione graduale da C++/CX a C++/WinRT.There are some limitations to porting gradually from C++/CX to C++/WinRT. Nel caso di un progetto di componente Windows Runtime, la conversione graduale non è possibile ed è necessario convertire il progetto in un unico passaggio.If you have a Windows Runtime component project, then porting gradually is not possible, and you'll need to port the project in one pass. Nel caso di un progetto XAML, in un determinato momento, i tipi di pagina XAML devono essere o tutti C++/WinRT o tutti C++/CX.And for a XAML project, at any given time your XAML page types must be either all C++/WinRT or all C++/CX. Per altre informazioni, vedere l'argomento Passare a C++/WinRT da C++/CX.For more info, see the topic Move to C++/WinRT from C++/CX.

Motivo per cui un intero argomento è dedicato all'interoperabilità del codice asincronoThe reason an entire topic is dedicated to asynchronous code interop

La conversione da C++/CX a C++/WinRT è in genere semplice, con la sola eccezione del passaggio dalle attività della libreria PPL (Parallel Patterns Library) alle coroutine.Porting from C++/CX to C++/WinRT is generally straightforward, with the one exception of moving from Parallel Patterns Library (PPL) tasks to coroutines. I modelli sono diversi.The models are different. Non esiste un mapping uno-a-uno naturale dalle attività della libreria PPL alle coroutine e non esiste un modo semplice, adatto a tutti i casi, per convertire meccanicamente il codice.There isn't a natural one-to-one mapping from PPL tasks to coroutines, and there's no simple way (that works for all cases) to mechanically port the code.

Il vantaggio è che la conversione dalle attività alle coroutine comporta semplificazioni significative.The good news is that conversion from tasks to coroutines results in significant simplifications. Inoltre, i team di sviluppo periodicamente segnalano che, una volta superati gli ostacoli tipici della conversione del codice asincrono, il resto dell'operazione di conversione è sostanzialmente meccanico.And development teams routinely report that once they're over the hurdle of porting their asynchronous code, the remainder of the porting work is largely mechanical.

Spesso, un algoritmo viene scritto originariamente per adattarsi alle API sincrone.Often, an algorithm was originally written to suit synchronous APIs. Successivamente viene tradotto in attività e continuazioni esplicite. Il risultato è spesso un offuscamento involontario della logica sottostante.And then that was translated into tasks and explicit continuations—the result often being an inadvertent obfuscation of the underlying logic. Ad esempio, i cicli diventano ricorsioni; i rami if-else diventano un albero annidato, ovvero una catena, di attività e le variabili condivise diventano shared_ptr.For example, loops become recursion; if-else branches turn into a nested tree (a chain) of tasks; shared variables become shared_ptr. Per decostruire la struttura spesso non naturale del codice sorgente della libreria PPL, è consigliabile prima di tutto fare un passo indietro e comprendere lo scopo del codice originale, ovvero individuare la versione sincrona originale.To deconstruct the often unnatural structure of PPL source code, we recommend that you first step back and understand the intent of the original code (that is, discover the original synchronous version). A questo punto, inserire co_await (attendere in modo cooperativo) nelle posizioni appropriate.And then insert co_await (cooperatively await) into the appropriate places.

Per questo motivo, se si dispone di una versione C# (piuttosto che C++/CX) del codice asincrono da cui iniziare la conversione, si può ottenere una procedura più snella e una conversione più pulita.For that reason, if you have a C# (rather than C++/CX) version of the asynchronous code from which to begin your port, then that can give you an easier time, and a cleaner port. Il codice C# usa await.C# code uses await. Quindi il codice C# prevede l'inizio con una versione sincrona e quindi l'inserimento di await nelle posizioni appropriate.So C# code already essentially follows a philosophy of beginning with a synchronous version and then inserting await into the appropriate places.

Se non è disponibile una versione C# del progetto, è possibile usare le tecniche descritte in questo argomento.If you don't have a C# version of your project, then you can use the techniques described in this topic. Dopo aver eseguito la conversione in C++/WinRT, la struttura del codice asincrono sarà più semplice da convertire in C#, se lo si desidera.And once you've ported to C++/WinRT, the structure of your async code will then be easier to port to C#, should you wish to.

Alcune informazioni di base sulla programmazione asincronaSome background in asynchronous programming

Per avere un quadro di riferimento comune sui concetti e sulla terminologia della programmazione asincrona, tenere presenti alcuni aspetti relativi alla programmazione asincrona di Windows Runtime in generale, nonché la modalità con cui le due proiezioni del linguaggio C++, ciascuna in modi diversi, si sovrappongono a tale tipo di programmazione.So that we have a common frame of reference for asynchronous programming concepts and terminology, let's briefly set the scene regarding Windows Runtime asynchronous programming in general, and also how the two C++ language projections are each, in their different ways, layered on top of that.

Il progetto è costituito da metodi che funzionano in modo asincrono. Esistono due tipi principali.Your project has methods that do work asynchronously, and there are two main kinds.

  • In genere è opportuno attendere il completamento di un'operazione asincrona prima di eseguire altre operazioni.It's common to want to wait on the completion of asynchronous work before you do something else. Un metodo che restituisce un oggetto operazione asincrona richiede un intervallo di tempo di attesa.A method that returns an asynchronous operation object is one that you can wait on.
  • A volte, tuttavia, non si vuole o non è necessario attendere il completamento di un'operazione eseguita in modo asincrono.But sometimes you don't want or need to wait on the completion of work done asynchronously. In questo caso è più efficiente per il metodo asincrono non restituire un oggetto operazione asincrona.In that case it's more efficient for the asynchronous method not to return an asynchronous operation object. Un metodo asincrono come questo, ovvero un metodo che non include un intervallo di tempo di attesa, è definito come metodo fire-and-forget.An asynchronous method such as that—one that you don't wait on—is known as a fire-and-forget method.

Oggetti asincroni di Windows Runtime (IAsyncXxx)Windows Runtime async objects (IAsyncXxx)

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.

In questo argomento, quando si usa la forma abbreviata IAsyncXxx, si fa riferimento a questi tipi collettivamente o a uno dei quattro tipi senza specificare quale.In this topic, when we use the convenient shorthand of IAsyncXxx, we're referring either to these types collectively; or we're talking about one of the four types without needing to specify which one.

Codice C++/CX asincronoC++/CX async

Il codice C++/CX asincrono usa le attività della libreria PPL (Parallel Patterns Library).Asynchronous C++/CX code makes use of Parallel Patterns Library (PPL) tasks. Un'attività PPL è rappresentata dalla classe concurrency::task.A PPL task is represented by the concurrency::task class.

In genere, un metodo C++/CX asincrono concatena le attività della libreria PPL insieme usando funzioni lambda con concurrency::create_task e concurrency::task::then.Typically, an asynchronous C++/CX method chains PPL tasks together by using lambda functions with concurrency::create_task and concurrency::task::then. Ognuna funzione lambda restituisce un'attività che, quando viene completata, genera un valore che viene passato all'espressione lambda di continuazione dell'attività.Each lambda function returns a task which, when it completes, produces a value that is then passed into the lambda of the task's continuation.

In alternativa, invece di chiamare create_task per creare un'attività, un metodo C++/CX asincrono può chiamare concurrency::create_async per creare un oggetto IAsyncXxx^.Alternatively, instead of calling create_task to create a task, an asynchronous C++/CX method can call concurrency::create_async to create an IAsyncXxx^.

Il tipo restituito di un metodo C++/CX asincrono può quindi essere un'attività della libreria PPL oppure un oggetto IAsyncXxx^.So the return type of an asynchronous C++/CX method can be a PPL task, or an IAsyncXxx^.

In entrambi i casi, il metodo stesso usa la parola chiave return per restituire un oggetto asincrono che, al termine, produce il valore effettivamente desiderato dal chiamante (ad esempio un file, una matrice di byte o un valore booleano).In either case, the method itself uses the return keyword to return an asynchronous object which, when it completes, produces the value that the caller actually wants (perhaps a file, an array of bytes, or a Boolean).

Nota

Se un metodo C++/CX asincrono restituisce un oggetto IAsyncXxx^, l'oggetto TResult (se presente) è limitato a essere un tipo di Windows Runtime.If an asynchronous C++/CX method returns an IAsyncXxx^, then the TResult (if any) is limited to being a Windows Runtime type. Un valore booleano, ad esempio, è un tipo di Windows Runtime, mentre un tipo proiettato C++/CX (ad esempio Platform::Array ^) non lo è.A Boolean value, for example, is a Windows Runtime type; but a C++/CX projected type (for example, Platform::Array^) is not.

Codice C++/WinRT asincronoC++/WinRT async

C++/WinRT integra le coroutine di C++ nel modello di programmazione.C++/WinRT integrates C++ coroutines into the programming model. Le coroutine e l'istruzione co_await forniscono un metodo naturale per attendere in modo cooperativo un risultato.Coroutines and the co_await statement provide a natural way to cooperatively wait for a result.

Ognuno dei tipi IAsyncXxx viene proiettato in un tipo corrispondente nello spazio dei nomi C++/WinRT winrt::Windows::Foundation.Each of the IAsyncXxx types is projected into a corresponding type in the winrt::Windows::Foundation C++/WinRT namespace. A tali tipi viene fatto riferimento con il nome winrt::IAsyncXxx (rispetto al nome IAsyncXxx^ di C++/CX).Let's refer to those as winrt::IAsyncXxx (as compared to the IAsyncXxx^ of C++/CX).

Il tipo restituito di una coroutine C++/WinRT può essere winrt::IAsyncXxx o winrt::fire_and_forget.The return type of a C++/WinRT coroutine is either a winrt::IAsyncXxx, or winrt::fire_and_forget. Invece di usare la parola chiave return per restituire un oggetto asincrono, una coroutine usa la parola chiave co_return per restituire in modo cooperativo il valore effettivamente desiderato dal chiamante (ad esempio, un file, una matrice di byte o un valore booleano).And instead of using the return keyword to return an asynchronous object, a coroutine uses the co_return keyword to cooperatively return the value that the caller actually wants (perhaps a file, an array of bytes, or a Boolean).

Se un metodo contiene almeno un'istruzione co_await (o almeno un'istruzione co_return o co_yield), il metodo può definirsi una coroutine.If a method contains at least one co_await statement (or at least one co_return or co_yield), then the method is a coroutine for that reason.

Per altre informazioni ed esempi di codice, vedi Concorrenza e operazioni asincrone con C++/WinRT.For more info, and code examples, see Concurrency and asynchronous operations with C++/WinRT.

Esempio di gioco Direct3D (Simple3DGameDX)The Direct3D game sample (Simple3DGameDX)

Questo argomento contiene procedure dettagliate di diverse tecniche di programmazione specifiche che illustrano come convertire gradualmente il codice asincrono.This topic contains walkthroughs of several specific programming techniques that illustrate how to gradually port asynchronous code. Come case study, verrà usata la versione di C++/CX dell'esempio di gioco Direct3D (denominata Simple3DGameDX).To serve as a case study, we'll be using the C++/CX version of the Direct3D game sample (which is called Simple3DGameDX). Verranno illustrati alcuni esempi di come è possibile acquisire il codice sorgente C++/CX originale in tale progetto e convertire gradualmente il codice asincrono in C++/WinRT.We'll show some examples of how you can take the original C++/CX source code in that project and gradually port its asynchronous code to C++/WinRT.

  • Scaricare il file con estensione zip dal collegamento precedente e decomprimerlo.Download the ZIP from the link above, and unzip it.
  • Aprire il progetto C++/CX (si trova nella cartella denominata cpp) in Visual Studio.Open the C++/CX project (it's in the folder named cpp) in Visual Studio.
  • Sarà quindi necessario aggiungere il supporto di C++/WinRT al progetto.You'll then need to add C++/WinRT support to the project. La procedura da seguire per questa operazione è descritta in Acquisizione di un progetto C++/CX e aggiunta del supporto di C++/WinRT.The steps that you follow to do that are described in Taking a C++/CX project and adding C++/WinRT support. In questa sezione il passaggio relativo all'aggiunta del file di intestazione interop_helpers.h al progetto è particolarmente importante perché in questo argomento si farà riferimento alle funzioni helper.In that section, the step about adding the interop_helpers.h header file to your project is particularly important because we'll be depending on those helper functions in this topic.
  • Infine, aggiungere #include <pplawait.h> a pch.h.Finally, add #include <pplawait.h> to pch.h. In questo modo, si ottiene il supporto della coroutine per la libreria PPL. Altre informazioni su tale supporto sono illustrate nella sezione seguente.That gives you coroutine support for PPL (there's more about that support in the following section).

Non eseguire ancora la compilazione, altrimenti verranno generati errori relativi a byte ambigui.Don't build yet, otherwise you'll get errors about byte being ambiguous. Ecco come risolverli.Here's how to resolve that.

  • Aprire BasicLoader.cpp e aggiungere il commento using namespace std;.Open BasicLoader.cpp, and comment out using namespace std;.
  • Nello stesso file di codice sorgente sarà quindi necessario qualificare shared_ptr come std::shared_ptr.In that same source code file, you'll then need to qualify shared_ptr as std::shared_ptr. A tale scopo, eseguire un'operazione di ricerca e sostituzione nel file.You can do that with a search-and-replace within that file.
  • Qualificare quindi vector come std::vector e string come std::string.Then qualify vector as std::vector, and string as std::string.

Il progetto ora viene ricompilato, dispone del supporto di C++/WinRT e contiene le funzioni helper di interoperabilità from_cx e to_cx.The project now builds again, has C++/WinRT support, and contains the from_cx and to_cx interop helper functions.

Il progetto Simple3DGameDX è ora pronto per essere gestito con le procedure dettagliate relative al codice descritte in questo argomento.You now have the Simple3DGameDX project ready to follow along with the code walkthroughs in this topic.

Panoramica della conversione dal codice C++/CX asincrono a C++/WinRTOverview of porting C++/CX async to C++/WinRT

In breve, in fase di conversione si modificano le attività della libreria PPL in chiamate a co_await.In a nutshell, as we port, we'll be changing PPL task chains into calls to co_await. Il valore restituito di un metodo verrà modificato da un'attività della libreria PPL a un oggetto winrt::IAsyncXxx di C++/WinRT.We'll be changing the return value of a method from a PPL task to a C++/WinRT winrt::IAsyncXxx object. Verrà anche modificato qualsiasi oggetto IAsyncXxx^ in un oggetto winrt::IAsyncXxx di C++/WinRT.And we'll also be changing any IAsyncXxx^ to a C++/WinRT winrt::IAsyncXxx.

Si noterà che una coroutine corrisponde a un metodo che chiama co_xxx.You'll recall that a coroutine is any method that calls co_xxx. Una coroutine di C++/WinRT usa co_return per restituirne il valore in modo cooperativo.A C++/WinRT coroutine uses co_return to cooperatively return its value. Grazie al supporto della coroutine per la libreria PPL (fornito da pplawait.h), è possibile usare co_return per restituire un'attività della libreria PPL da una coroutine.Thanks to the coroutine support for PPL (courtesy of pplawait.h), you can also use co_return to return a PPL task from a coroutine. Ed è anche possibile usare co_await per restituire sia le attività che l'oggetto IAsyncXxx.And you can also co_await both tasks and IAsyncXxx. Non è tuttavia possibile usare co_return per restituire un oggetto IAsyncXxx^.But you can't use co_return to return an IAsyncXxx^. La tabella seguente descrive il supporto per l'interoperabilità tra le varie tecniche asincrone con pplawait.h nell'immagine.The table below describes support for interop between the various asynchronous techniques with pplawait.h in the picture.

MetodoMethod È possibile usare co_await?Can you co_await it? È possibile usare co_return?Can you co_return from it?
Il metodo restituisce task<void>Method returns task<void> Yes Yes
Il metodo restituisce task<T>Method returns task<T> NoNo Yes
Il metodo restituisce IAsyncXxx^Method returns IAsyncXxx^ Yes No.No. Si esegue, tuttavia, il wrapping di create_async in un'attività che usa co_return.But you wrap create_async around a task that uses co_return.
Il metodo restituisce winrt::IAsyncXxxMethod returns winrt::IAsyncXxx Yes Yes

Usare la tabella seguente per passare direttamente alla sezione di questo argomento che descrive la tecnica di interoperabilità di interesse oppure continuare a leggere da qui.Use this next table to jump straight to the section in this topic that describes an interop technique of interest, or just continue reading from here.

Tecnica di interoperabilità asincronaAsync interop technique Sezione di questo argomentoSection in this topic
Usare co_await per attendere un metodo task<void> da un metodo fire-and-forget o da un costruttore.Use co_await to await a task<void> method from within a fire-and-forget method, or within a constructor. Attendere task<void> in un metodo fire-and-forgetAwait task<void> within a fire-and-forget method
Usare co_await per attendere un metodo task<void> da un metodo task<void> .Use co_await to await a task<void> method from within a task<void> method. Attendere task<void> in un metodo task<void> Await task<void> within a task<void> method
Usare co_await per attendere un metodo task<void> da un metodo task<T> .Use co_await to await a task<void> method from within a task<T> method. Attendere task<void> in un metodo task<T> Await task<void> within a task<T> method
Usare co_await per attendere un metodo IAsyncXxx^.Use co_await to await an IAsyncXxx^ method. Attendere un metodo IAsyncXxx^ in un metodo task, lasciando invariato il resto del progettoAwait an IAsyncXxx^ in a task method, leaving the rest of the project unchanged
Usare co_return in un metodo task<void> .Use co_return within a task<void> method. Attendere task<void> in un metodo task<void> Await task<void> within a task<void> method
Usare co_return in un metodo task<T> .Use co_return within a task<T> method. Attendere un metodo IAsyncXxx^ in un metodo task, lasciando invariato il resto del progettoAwait an IAsyncXxx^ in a task method, leaving the rest of the project unchanged
Eseguire il wrapping di create_async in un'attività che usa co_return.Wrap create_async around a task that uses co_return. Eseguire il wrapping di create_async in un'attività che usa co_returnWrap create_async around a task that uses co_return
Convertire concurrency::wait.Port concurrency::wait. Convertire concurrency::wait in co_await winrt::resume_afterPort concurrency::wait to co_await winrt::resume_after
Restituire winrt::IAsyncXxx invece di task<void> .Return winrt::IAsyncXxx instead of task<void>. Convertire un tipo restituito task<void> in winrt::IAsyncXxxPort a task<void> return type to winrt::IAsyncXxx
Convertire un elemento winrt::IAsyncXxx<T> (T è primitiva) in un elemento task<T> .Convert a winrt::IAsyncXxx<T> (T is primitive) to a task<T>. Convertire un elemento winrt::IAsyncXxx<T> (T è primitiva) in un elemento task<T> Convert a winrt::IAsyncXxx<T> (T is primitive) to a task<T>
Convertire un elemento winrt::IAsyncXxx<T> (T è un tipo di Windows Runtime) in un elemento task<T^> .Convert a winrt::IAsyncXxx<T> (T is a Windows Runtime type) to a task<T^>. Convertire un elemento winrt::IAsyncXxx<T> (T è un tipo di Windows Runtime) in un elemento task<T^> Convert a winrt::IAsyncXxx<T> (T is a Windows Runtime type) to a task<T^>

Ecco un breve esempio di codice che illustra una parte del supporto.And here's a short code example illustrating some of the support.

#include <ppltasks.h>
#include <pplawait.h>
#include <winrt/Windows.Foundation.h>

concurrency::task<bool> TaskAsync()
{
    co_return true;
}

Windows::Foundation::IAsyncOperation<bool>^ IAsyncXxxCppCXAsync()
{
    // co_return true; // Error! Can't do that. But you can do
    // the following.
    return concurrency::create_async([=]() -> concurrency::task<bool> {
        co_return true;
        });
}

winrt::Windows::Foundation::IAsyncOperation<bool> IAsyncXxxCppWinRTAsync()
{
    co_return true;
}

concurrency::task<bool> CppCXAsync()
{
    bool b1 = co_await TaskAsync();
    bool b2 = co_await IAsyncXxxCppCXAsync();
    co_return co_await IAsyncXxxCppWinRTAsync();
}

winrt::fire_and_forget CppWinRTAsync()
{
    bool b1 = co_await TaskAsync();
    bool b2 = co_await IAsyncXxxCppCXAsync();
    bool b3 = co_await IAsyncXxxCppWinRTAsync();
}

Importante

Anche con queste eccezionali opzioni di interoperabilità, la conversione graduale dipende dalla scelta delle modifiche specifiche che è possibile apportare in modo che non influiscano sul resto del progetto.Even with these great interop options, porting gradually depends on choosing changes that we can make surgically that don't affect the rest of the project. L'obiettivo è evitare di trovarsi impreparati e, di conseguenza, compromettere la struttura dell'intero progetto.We want to avoid tugging at an arbitrary loose end, and thereby unraveling the structure of the whole project. Per questo motivo, è necessario eseguire le operazioni in un ordine specifico.For that, we have to do things in a particular order. Di seguito sono riportati alcuni esempi di creazione di questi tipi di modifiche di conversione/interoperabilità asincrona.Next we'll look closely at some examples of making these kinds of async-related porting/interop changes.

Attendere un metodo task<void> , lasciando invariato il resto del progettoAwait a task<void> method, leaving the rest of the project unchanged

Un metodo che restituisce task<void> esegue l'operazione in modo asincrono restituendo un oggetto operazione asincrona, ma non genera un valore.A method that returns task<void> performs work asynchronously, and it returns an asynchronous operation object, but it doesn't ultimately produce a value. È possibile co_await un metodo come questo.We can co_await a method like that.

Quindi, un'ottima soluzione per iniziare a convertire gradualmente il codice asincrono consiste nel trovare le posizioni in cui chiamare tali metodi.So a good place to start porting async code gradually is to find places where you call such methods. Queste posizioni comporteranno la creazione e/o la restituzione di un'attività.Those places will involve creating and/or returning a task. Possono interessare anche il tipo di catena di attività in cui nessun valore viene passato da ogni attività alla relativa continuazione.They might also involve the kind of task chain where no value is passed from each task to its continuation. In queste posizioni è possibile sostituire semplicemente il codice asincrono con le istruzioni co_await, come illustrato più avanti.In places like that, you can just replace the async code with co_await statements, as we'll see.

Nota

Andando avanti nell'argomento, si vedrà il vantaggio di questa strategia.As this topic progresses, you'll see the benefit to this strategy. Dopo che un determinato metodo task<void> viene chiamato esclusivamente tramite co_await, è possibile convertire il metodo in C++/WinRT e fare in modo che restituisca un elemento winrt::IAsyncXxx.Once a particular task<void> method is being called exclusively via co_await, you're then free to port that method to C++/WinRT, and have it return a winrt::IAsyncXxx.

Ecco alcuni esempi.Let's find some examples. Aprire il progetto Simple3DGameDX. Vedere l'esempio di gioco Direct3D.Open the Simple3DGameDX project (see The Direct3D game sample).

Importante

Negli esempi seguenti, quando si osservano le modifiche apportate alle implementazioni dei metodi, tenere presente che non è necessario modificare i chiamanti dei metodi che si stanno modificando.In the examples that follow, as you see the implementations of methods being changed, bear in mind that we don't need to change the callers of the methods we're changing. Queste modifiche sono localizzate e non si propagano a catena nel progetto.These changes are localized, and they don't cascade through the project.

Attendere task<void> in un metodo fire-and-forgetAwait task<void> within a fire-and-forget method

Viene descritta l'attesa di task<void> in metodi fire-and-forget, perché questo è il caso più semplice.Let's start with awaiting task<void> within fire-and-forget methods, since that's the simplest case. Si tratta di metodi che funzionano in modo asincrono, ma il chiamante del metodo non attende il completamento dell'operazione.These are methods that do work asynchronously, but the caller of the method doesn't wait for that work to complete. È sufficiente chiamare il metodo e non occuparsene più, nonostante il fatto che si completi in modo asincrono.You just call the method and forget it, despite the fact that it completes asynchronously.

Esaminare la radice del grafico delle dipendenze del progetto per i metodi void che contengono create_task e/o le catene di attività in cui vengono chiamati solo i metodi <void>task.Look toward the root of your project's dependency graph for void methods that contain create_task and/or task chains where only task<void> methods are called.

In Simple3DGameDX è presente codice come questo nell'implementazione del metodo GameMain::Update.In Simple3DGameDX, you'll find code like that in the implementation of the method GameMain::Update. Si trova nel file del codice sorgente GameMain.cpp.It's in the source code file GameMain.cpp.

GameMain::UpdateGameMain::Update

Di seguito è riportato un estratto della versione C++/CX del metodo che illustra le due parti del metodo che vengono eseguite in modo asincrono.Here's an extract from the C++/CX version of the method, showing the two parts of the method that complete asynchronously.

void GameMain::Update()
{
    ...
    case UpdateEngineState::WaitingForPress:
        ...
        m_game->LoadLevelAsync().then([this]()
        {
            m_game->FinalizeLoadLevel();
            m_updateState = UpdateEngineState::ResourcesLoaded;
        }, task_continuation_context::use_current());
        ...
    case UpdateEngineState::Dynamics:
        ...
        m_game->LoadLevelAsync().then([this]()
        {
            m_game->FinalizeLoadLevel();
            m_updateState = UpdateEngineState::ResourcesLoaded;
        }, task_continuation_context::use_current());
        ...
    ...
}

È possibile visualizzare una chiamata al metodo Simple3DGame::LoadLevelAsync (che restituisce un oggetto task<void> della libreria PPL).You can see a call to the Simple3DGame::LoadLevelAsync method (which returns a PPL task<void>). Successivamente, si può osservare una continuazione che esegue alcune operazioni sincrone.After that is a continuation that does some synchronous work. LoadLevelAsync è un'operazione asincrona, ma non restituisce alcun valore.LoadLevelAsync is asynchronous, but it doesn't return a value. Non viene quindi passato alcun valore dall'attività alla continuazione.So no value is being passed from the task to the continuation.

È possibile apportare lo stesso tipo di modifica al codice in queste due posizioni.We can make the same kind of change to the code in these two places. Il codice viene illustrato dopo il listato seguente.The code is explained after the listing below. Si potrebbe descrivere l'accesso sicuro al puntatore this in una coroutine membro di classe,We could have a discussion here about the safe way to access the this pointer in a class-member coroutine. ma per il momento è opportuno rimandare la trattazione a una sezione successiva (Discussione rinviata su co_await e sul puntatore this). Il codice funziona.But let's defer that for a later section (The deferred discussion about co_await and the this pointer)—for now, this code works.

winrt::fire_and_forget GameMain::Update()
{
    ...
    case UpdateEngineState::WaitingForPress:
        ...
        co_await m_game->LoadLevelAsync();
        m_game->FinalizeLoadLevel();
        m_updateState = UpdateEngineState::ResourcesLoaded;
        ...
    case UpdateEngineState::Dynamics:
        ...
        co_await m_game->LoadLevelAsync();
        m_game->FinalizeLoadLevel();
        m_updateState = UpdateEngineState::ResourcesLoaded;
        ...
    ...
}

Come si può notare, poiché LoadLevelAsync restituisce un'attività, è possibile co_await tale operazione.As you can see, because LoadLevelAsync returns a task, we can co_await it. Non è quindi necessaria una continuazione esplicita: il codice che segue co_await viene eseguito solo al completamento di LoadLevelAsync.And we don't need an explicit continuation—the code that follows a co_await executes only when LoadLevelAsync completes.

L'introduzione di co_await trasforma il metodo in una coroutine, pertanto non è possibile lasciare che restituisca void.Introducing the co_await turns the method into a coroutine, so we couldn't leave it returning void. Si tratta di un metodo fire-and-forget e quindi è stato modificato in modo che restituisca winrt::fire_and_forget.It's a fire-and-forget method, so we changed it to return winrt::fire_and_forget.

Sarà anche necessario modificare GameMain.h.You'll also need to edit GameMain.h. Modificare il tipo restituito di GameMain::Update da void a winrt::fire_and_forget anche nella dichiarazione.Change the return type of GameMain::Update from void to winrt::fire_and_forget in the declaration there, too.

È possibile apportare questa modifica alla copia del progetto e il gioco viene comunque compilato ed eseguito.You can make this change to your copy of the project, and the game still builds and runs the same. Il codice sorgente è fondamentalmente ancora C++/CX, ma ora usa gli stessi modelli di C++/WinRT. A questo punto, la possibilità di convertire il resto del codice in modo meccanico è più concreta.The source code is still fundamentally C++/CX, but it's now using the same patterns as C++/WinRT, so that has moved us a little closer to being able to port the rest of the code mechanically.

GameMain::ResetGameGameMain::ResetGame

GameMain::ResetGame è un altro metodo fire-and-forget che chiama LoadLevelAsync.GameMain::ResetGame is another fire-and-forget method; it calls LoadLevelAsync, too. È quindi possibile apportare la stessa modifica del codice se si vuole fare pratica.So you can make the same code change there if you want the practice.

GameMain::OnDeviceRestoredGameMain::OnDeviceRestored

La questione si fa più interessante in GameMain::OnDeviceRestored perché è costituito da un annidamento più profondo di codice asincrono, inclusa un'attività senza operazioni.Things get a little more interesting in GameMain::OnDeviceRestored because of its deeper nesting of async code, including a no-op task. Ecco una descrizione delle parti asincrone del metodo (con le porzioni meno interessanti del codice sincrono rappresentate da puntini di sospensione).Here's an outline of the asynchronous parts of the method (with the less-interesting synchronous code represented by ellipses).

void GameMain::OnDeviceRestored()
{
    ...
    create_task([this]()
    {
        return m_renderer->CreateGameDeviceResourcesAsync(m_game);
    }).then([this]()
    {
        ...
        if (m_updateState == UpdateEngineState::WaitingForResources)
        {
            ...
            return m_game->LoadLevelAsync().then([this]()
            {
                ...
            }, task_continuation_context::use_current());
        }
        else
        {
            return create_task([]()
            {
                // Return a no-op task.
            });
        }
    }, task_continuation_context::use_current()).then([this]()
    {
        ...
    }, task_continuation_context::use_current());
}

Per prima cosa, modificare il tipo restituito di GameMain::OnDeviceRestored da void a winrt::fire_and_forget in GameMain.h e .cpp.First, change the return type of GameMain::OnDeviceRestored from void to winrt::fire_and_forget in GameMain.h and .cpp. Sarà inoltre necessario aprire DeviceResources.h e apportare la stessa modifica al tipo restituito di IDeviceNotify::OnDeviceRestored.You'll also need to open DeviceResources.h and make the same change to the return type of IDeviceNotify::OnDeviceRestored.

Per convertire il codice asincrono, rimuovere tutte le chiamate create_task e then e le relative parentesi graffe e semplificare il metodo in una serie di istruzioni flat.To port the async code, remove all of the create_task and then calls and their curly brackets, and simplify the method into a flat series of statements.

Modificare qualsiasi operazione return che restituisce un'attività in un'operazione co_await.Change any return that returns a task into a co_await. Rimarrà un'operazione return che non restituisce alcun elemento ed è quindi sufficiente eliminarla.You'll be left with one return that returns nothing, so just delete that. Al termine, l'attività senza operazioni sarà scomparsa e l'aspetto delle parti asincrone del metodo sarà simile al seguente.When you're done, the no-op task will have disappeared, and the outline of the asynchronous parts of the method will look like this. Anche in questo caso, le parti meno interessanti del codice sincrono sono sostituite da puntini di sospensione.Again, the less-interesting synchronous code is elided.

winrt::fire_and_forget GameMain::OnDeviceRestored()
{
    ...
    co_await m_renderer->CreateGameDeviceResourcesAsync(m_game);
    ...
    if (m_updateState == UpdateEngineState::WaitingForResources)
    {
        co_await m_game->LoadLevelAsync();
        ...
    }
    ...
}

Come si può notare, questa forma di struttura asincrona è molto più semplice e più facile da leggere.As you can see, this form of async structure is significantly simpler, and easier to read.

GameMain::GameMainGameMain::GameMain

Il costruttore GameMain::GameMain esegue l'operazione in modo asincrono e nessuna parte del progetto attende il completamento di tale operazione.The GameMain::GameMain constructor performs work asynchronously, and no part of the project waits for that work to complete. Anche in questo elenco vengono descritte le parti asincrone.Again, this listing outlines the asynchronous parts.

GameMain::GameMain(...) : ...
{
    ...
    create_task([this]()
    {
        ...
        return m_renderer->CreateGameDeviceResourcesAsync(m_game);
    }).then([this]()
    {
        ...
        if (m_updateState == UpdateEngineState::WaitingForResources)
        {
            return m_game->LoadLevelAsync().then([this]()
            {
                ...
            }, task_continuation_context::use_current());
        }
        else
        {
            return create_task([]()
            {
                // Return a no-op task.
            });
        }
    }, task_continuation_context::use_current()).then([this]()
    {
        ....
    }, task_continuation_context::use_current());
}

Un costruttore non può tuttavia restituire winrt::fire_and_forget e quindi il codice asincrono verrà spostato in un nuovo metodo fire-and-forget GameMain::ConstructInBackground, il codice verrà appiattito in istruzioni co_await e verrà chiamato il nuovo metodo dal costruttore.But a constructor can't return winrt::fire_and_forget, so we'll move the asynchronous code into a new GameMain::ConstructInBackground fire-and-forget method, flatten the code into co_await statements, and call the new method from the constructor. Ecco il risultato.Here's the result.

GameMain::GameMain(...) : ...
{
    ...
    ConstructInBackground();
}

winrt::fire_and_forget GameMain::ConstructInBackground()
{
    ...
    co_await m_renderer->CreateGameDeviceResourcesAsync(m_game);
    ...
    if (m_updateState == UpdateEngineState::WaitingForResources)
    {
        ...
        co_await m_game->LoadLevelAsync();
        ...
    }
    ...
}

Ora tutti i metodi fire-and-forget, ovvero tutto il codice asincrono, in GameMain sono stati trasformati in coroutine.Now all of the fire-and-forget methods—in fact, all of the async code— in GameMain has been turned into coroutines. Se si è interessati, è possibile cercare metodi fire-and-forget in altre classi e apportare modifiche simili.If you feel so inclined, perhaps you could look for fire-and-forget methods in other classes, and make similar changes.

Discussione rinviata su co_await e il puntatore thisThe deferred discussion about co_await and the this pointer

Quando sono state descritte le modifiche apportate a GameMain::Update, è stato rinviato un approfondimento sul puntatore this.When we were making changes to GameMain::Update, I deferred a discussion about the this pointer. In questa sezione, viene ripreso il discorso.Let's have that discussion here.

Si applica a tutti i metodi modificati finora e a tutte le coroutine, non solo a quelle di tipo fire-and-forget.This applies to all of the methods we've changed so far; and it applies to all coroutines, not just fire-and-forget ones. L'introduzione di co_await in un metodo comporta l'inserimento di un punto di sospensione.Introducing a co_await into a method introduces a suspension point. Per questo motivo, è necessario prestare attenzione al puntatore this, che naturalmente si usa dopo il punto di sospensione ogni volta che si accede a un membro di classe.And because of that, we have to be careful with the this pointer, which of course we make use of after the suspension point each time we access a class member.

In breve, la soluzione consiste nel chiamare implements::get_strong.The short story is that the solution is to call implements::get_strong. Per una descrizione completa del problema e della soluzione, vedere Accesso sicuro al puntatore this in una coroutine membro di classe.But for a complete discussion of the issue and the solution, see Safely accessing the this pointer in a class-member coroutine.

È possibile chiamare implements::get_strong solo in una classe che deriva da winrt::implements.You can call implements::get_strong only in a class that derives from winrt::implements.

Derivare GameMain da winrt::implementsDerive GameMain from winrt::implements

La prima modifica che è necessario apportare è in GameMain.h.The first change we need to make is in GameMain.h.

class GameMain :
    public DX::IDeviceNotify

GameMain continuerà a implementare DX::IDeviceNotify, ma verrà modificato in modo da derivare da winrt::implements.GameMain will continue to implement DX::IDeviceNotify, but we'll change it to derive from winrt::implements.

class GameMain : 
    public winrt::implements<GameMain, winrt::Windows::Foundation::IInspectable>,
    DX::IDeviceNotify

Quindi, in App.cpp, si troverà questo metodo.Next, in App.cpp, you'll find this method.

void App::Load(Platform::String^)
{
    if (!m_main)
    {
        m_main = std::unique_ptr<GameMain>(new GameMain(m_deviceResources));
    }
}

Tuttavia, ora che GameMain deriva da winrt::implements, è necessario crearlo in modo diverso.But now that GameMain derives from winrt::implements, we need to construct it in a different way. In questo caso, verrà usato il modello di funzione winrt::make_self.In this case, we'll use the winrt::make_self function template. Per altre informazioni, vedere Creazione di istanze e restituzione di interfacce e tipi di implementazione.For more info, see Instantiating and returning implementation types and interfaces.

Sostituire la riga di codice con questo.Replace that line of code with this.

    ...
    m_main = winrt::make_self<GameMain>(m_deviceResources);
    ...

Per chiudere il ciclo su tale modifica, sarà necessario modificare anche il tipo di m_main.To close the loop on that change, we'll also need to change the type of m_main. In App.h, si troverà questo codice:In App.h, you'll find this code.

ref class App sealed :
    public Windows::ApplicationModel::Core::IFrameworkView
{
    ...
private:
    ...
    std::unique_ptr<GameMain> m_main;
};

Modificare la dichiarazione di m_main come segue.Change that declaration of m_main to this.

    ...
    winrt::com_ptr<GameMain> m_main;
    ...

È ora possibile chiamare implements::get_strongWe can now call implements::get_strong

Per GameMain::Update e per uno degli altri metodi a cui è stata aggiunta una coroutine co_await, di seguito viene illustrato come è possibile chiamare get_strong all'inizio di una coroutine per garantire che un riferimento sicuro sopravviva fino al completamento della coroutine.For GameMain::Update, and for any of the other methods we added a co_await to, here's how you can call get_strong at the beginning of a coroutine to ensure that a strong reference survives until the coroutine completes.

winrt::fire_and_forget GameMain::Update()
{
    auto strong_this{ get_strong() }; // Keep *this* alive.
    ...
        co_await ...
    ...
}

Attendere task<void> in un metodo task<void>Await task<void> within a task<void> method

Il caso più semplice successivo è l'attesa di task<void> in un metodo che a sua volta restituisce task<void> .The next simplest case is awaiting task<void> within a method that itself returns task<void>. Questo perché è possibile usare co_await per un'operazione task<void> ed è possibile usare co_return da un oggetto dello stesso tipo.That's because we can co_await a task<void>, and we can co_return from one.

Nell'implementazione del metodo Simple3DGame::LoadLevelAsync è disponibile un esempio molto semplice.You'll find a very simple example in the implementation of the method Simple3DGame::LoadLevelAsync. È nel file del codice sorgente Simple3DGame.cpp.It's in the source code file Simple3DGame.cpp.

task<void> Simple3DGame::LoadLevelAsync()
{
    m_level[m_currentLevel]->Initialize(m_objects);
    m_levelDuration = m_level[m_currentLevel]->TimeLimit() + m_levelBonusTime;
    return m_renderer->LoadLevelResourcesAsync();
}

Esiste solo un codice sincrono, seguito dalla restituzione dell'attività creata da GameRenderer::LoadLevelResourcesAsync.There's just some synchronous code, followed by returning the task that's created by GameRenderer::LoadLevelResourcesAsync.

Anziché restituire l'attività, viene eseguita una coroutine co_await dell'attività e quindi una coroutine co_return dell'oggetto void risultante.Instead of returning that task, we co_await it, and then co_return the resulting void.

task<void> Simple3DGame::LoadLevelAsync()
{
    m_level[m_currentLevel]->Initialize(m_objects);
    m_levelDuration = m_level[m_currentLevel]->TimeLimit() + m_levelBonusTime;
    co_return co_await m_renderer->LoadLevelResourcesAsync();
}

Non sembra una modifica profonda.That doesn't look like a profound change. Tuttavia, ora che viene chiamato GameRenderer::LoadLevelResourcesAsync tramite co_await, è possibile convertirlo in modo che restituisca winrt::IAsyncXxx invece di un'attività.But now that we're calling GameRenderer::LoadLevelResourcesAsync via co_await, we're free to port it to return a winrt::IAsyncXxx instead of a task. Questa operazione verrà eseguita più avanti nella sezione Convertire un tipo restituito task<void> in winrt::IAsyncXxx.We'll do that later in the section Port a task<void> return type to winrt::IAsyncXxx.

Attendere task<void> in un metodo task<T>Await task<void> within a task<T> method

Anche se non esistono esempi appropriati in Simple3DGameDX, è possibile realizzare un esempio ipotetico solo per illustrare il modello.Although there are no suitable examples to be found in Simple3DGameDX, we can contrive a hypothetical example just to show the pattern.

La prima riga nell'esempio di codice seguente illustra solo la semplice coroutine co_await di un oggetto task<void> .The first line in the code example below demonstrates the simple co_await of a task<void>. Quindi, per soddisfare il tipo restituito task<T> , è necessario restituire in modo asincrono un elemento StorageFile^ .Then, in order to satisfy the task<T> return type, we need to asynchronously return a StorageFile^. A tale scopo, si usa co_await per un'API Windows Runtime e co_return per il file risultante.To do that, we co_await a Windows Runtime API, and co_return the resulting file.

task<StorageFile^> Simple3DGame::LoadLevelAndRetrieveFileAsync(
    StorageFolder^ location,
    Platform::String^ filename)
{
    co_await m_renderer->LoadLevelResourcesAsync();
    co_return co_await location->GetFileAsync(filename);
}

È addirittura possibile convertire una parte maggiore del metodo in C++/WinRT come segue.We could even port more of the method to C++/WinRT like this.

winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::Storage::StorageFile>
Simple3DGame::LoadLevelAndRetrieveFileAsync(
    StorageFolder location,
    std::wstring filename)
{
    co_await m_renderer->LoadLevelResourcesAsync();
    co_return co_await location.GetFileAsync(filename);
}

Il membro dati m_renderer è ancora in C++/CX nell'esempio.The m_renderer data member is still C++/CX in that example.

Attendere un metodo IAsyncXxx^ in un metodo task, lasciando invariato il resto del progettoAwait an IAsyncXxx^ in a task method, leaving the rest of the project unchanged

È stato già descritto come co_await un oggetto task<void> .We've seen how you can co_await task<void>. È anche possibile usare co_await per un metodo che restituisce IAsyncXxx, che si tratti di un metodo del progetto o di un'API Windows asincrona (ad esempio, StorageFolder.GetFileAsync, che è stata attesa in modo cooperativo nella sezione precedente).You can also co_await a method that returns an IAsyncXxx, whether that's a method in your project, or an asynchronous Windows API (for example, StorageFolder.GetFileAsync, which we cooperatively awaited in the previous section).

Per un esempio di come è possibile apportare questo tipo di modifica al codice, vedere BasicReaderWriter::ReadDataAsync, che verrà implementato in BasicReaderWriter.cpp.For an example of where we can make this kind of code change, let's look at BasicReaderWriter::ReadDataAsync (you'll find it implemented in BasicReaderWriter.cpp).

Di seguito è illustrata la versione originale di C++/CX.Here's the original C++/CX version.

task<Platform::Array<byte>^> BasicReaderWriter::ReadDataAsync(
    _In_ Platform::String^ filename
    )
{
    return task<StorageFile^>(m_location->GetFileAsync(filename)).then([=](StorageFile^ file)
    {
        return FileIO::ReadBufferAsync(file);
    }).then([=](IBuffer^ buffer)
    {
        auto fileData = ref new Platform::Array<byte>(buffer->Length);
        DataReader::FromBuffer(buffer)->ReadBytes(fileData);
        return fileData;
    });
}

Il listato di codice seguente mostra che è possibile usare co_await per API Windows che restituiscono IAsyncXxx^.The code listing below shows that we can co_await Windows APIs that return IAsyncXxx^. È possibile anche usare co_return per il valore restituito da BasicReaderWriter::ReadDataAsync in modo asincrono (in questo caso, una matrice di byte).Not only that, we can also co_return the value that BasicReaderWriter::ReadDataAsync returns asynchronously (in this case, an array of bytes). Il primo passaggio illustra come apportare solo queste modifiche. Il codice C++/CX verrà effettivamente convertito in C++/WinRT nella sezione successiva.This first step shows how to make just those changes; we'll actually port the C++/CX code to C++/WinRT in the next section.

task<Platform::Array<byte>^> BasicReaderWriter::ReadDataAsync(
    _In_ Platform::String^ filename
)
{
    StorageFile^ file = co_await m_location->GetFileAsync(filename);
    IBuffer^ buffer = co_await FileIO::ReadBufferAsync(file);
    auto fileData = ref new Platform::Array<byte>(buffer->Length);
    DataReader::FromBuffer(buffer)->ReadBytes(fileData);
    co_return fileData;
}

Anche in questo caso, non è necessario modificare i chiamanti dei metodi che vengono modificati, perché il tipo restituito non è stato modificato.Again, we don't need to change the callers of the methods we're changing, because we didn't change the return type.

Convertire ReadDataAsync (principalmente) in C++/WinRT, lasciando invariato il resto del progettoPort ReadDataAsync (mostly) to C++/WinRT, leaving the rest of the project unchanged

È possibile procedere ulteriormente e convertire il metodo quasi completamente in C++/WinRT senza dover modificare altre parti del progetto.We can go a step further and port the method almost entirely to C++/WinRT without needing to change any other part of the project.

L'unica dipendenza che questo metodo ha sul resto del progetto è il membro dati BasicReaderWriter::m_location, che è un oggetto StorageFolder^ di C++/CX.The only dependency that this method has on the rest of the project is the BasicReaderWriter::m_location data member, which is a C++/CX StorageFolder^. Per lasciare invariati il membro dati, il tipo di parametro e il tipo restituito, è necessario eseguire solo alcune conversioni — una all'inizio del metodo e una alla fine.To leave that data member unchanged, and to leave the parameter type and return type unchanged, we need only perform a couple of conversions—one at the beginning of the method, and one at the end. A tal fine, è possibile usare le funzioni helper di interoperabilità from_cx e to_cx.For that, we can use the from_cx and to_cx interop helper functions.

Ecco l'aspetto di BasicReaderWriter::ReadDataAsync dopo la conversione dell'implementazione prevalentemente in C++/WinRT.Here's how BasicReaderWriter::ReadDataAsync looks after porting its implementation predominantly to C++/WinRT. Si tratta di un esempio efficace di conversione graduale.This is a good example of porting gradually. Questo metodo si trova nella fase in cui è possibile non interpretarlo come un metodo C++/CX che usa alcune tecniche di C++/WinRT e vederlo come un metodo C++/WinRT che interagisce con C++/CX.And this method is at the stage where we can move away from thinking of it as a C++/CX method that uses some C++/WinRT techniques, and see it as a C++/WinRT method that interoperates with C++/CX.

#include <winrt/Windows.Storage.h>
#include <winrt/Windows.Storage.Streams.h>
#include <robuffer.h>
...
task<Platform::Array<byte>^> BasicReaderWriter::ReadDataAsync(
    _In_ Platform::String^ filename)
{
    auto location_from_cx = from_cx<winrt::Windows::Storage::StorageFolder>(m_location);

    auto file = co_await location_from_cx.GetFileAsync(filename->Data());
    auto buffer = co_await winrt::Windows::Storage::FileIO::ReadBufferAsync(file);
    byte* bytes;
    auto byteAccess = buffer.as<Windows::Storage::Streams::IBufferByteAccess>();
    winrt::check_hresult(byteAccess->Buffer(&bytes));

    co_return ref new Platform::Array<byte>(bytes, buffer.Length());
}

Nota

Sopra, in ReadDataAsync, viene creata e restituita una nuova matrice C++/CX.In ReadDataAsync above, we construct and return a new C++/CX array. L'operazione viene eseguita in modo da soddisfare il tipo restituito del metodo, per non dover modificare il resto del progetto.And of course we do that to satisfy the method's return type (so that we don't have to change the rest of the project).

È possibile che si trovino altri esempi nel progetto in cui, dopo la conversione, si raggiunge la fine del metodo e si dispone solo di un oggetto C++/WinRT.You may come across other examples in your own project where, after porting, you reach the end of the method and all you have is a C++/WinRT object. Per co_return l'oggetto, è sufficiente chiamare to_cx per convertirlo.To co_return that, just call to_cx to convert it. Sono disponibili altre informazioni e un esempio nella sezione successiva.There's more info about that, and an example, the next section.

Convertire un elemento winrt::IAsyncXxx<T> in un elemento task<T>Convert a winrt::IAsyncXxx<T> to a task<T>

Questa sezione descrive la situazione in cui è stato convertito un metodo asincrono in C++/WinRT (in modo che restituisca un elemento winrt::IAsyncXxx<T> ), ma è ancora presente codice C++/CX che chiama tale metodo come se continuasse a restituire un'attività.This section deals with the situation where you've ported an asynchronous method to C++/WinRT (so that it returns a winrt::IAsyncXxx<T>), but you still have C++/CX code calling that method as if it's still returning a task.

  • Un caso è quello in cui T è primitiva e non richiede alcuna conversione.One case is where T is primitive, which needs no conversion.
  • L'altro caso è quello in cui T è un tipo di Windows Runtime ed è quindi necessario convertirlo in T^.The other case is where T is a Windows Runtime type, in which case you'll need to convert that to a T^.

Convertire un elemento winrt::IAsyncXxx<T> (T è primitiva) in un elemento task<T>Convert a winrt::IAsyncXxx<T> (T is primitive) to a task<T>

Lo schema di questa sezione si applica quando viene restituito in modo asincrono un valore primitivo (verrà usato un valore booleano per la descrizione).The pattern in this section applies when you're asynchronously returning a primitive value (we'll use a Boolean value to illustrate). Si consideri un esempio in cui un metodo già convertito in C++/WinRT ha questa firma.Consider an example where a method that you've already ported to C++/WinRT has this signature.

winrt::Windows::Foundation::IAsyncOperation<bool>
MyClass::GetBoolMemberFunctionAsync()
{
    bool value = ...
    co_return value;
}

È possibile convertire una chiamata a tale metodo in un'attività simile alla seguente.You can convert a call to that method into a task like this.

task<bool> MyClass::RetrieveBoolTask()
{
    co_return co_await GetBoolMemberFunctionAsync();
}

In alternativa, è possibile una conversione simile alla seguente.Or like this.

task<bool> MyClass::RetrieveBoolTask()
{
    return concurrency::create_task(
        [this]() -> concurrency::task<bool> {
            auto result = co_await GetBoolMemberFunctionAsync();
            co_return result;
        });
}

Si noti che il tipo restituito task della funzione lambda è esplicito, perché il compilatore non può dedurlo.Notice that the task return type of the lambda function is explicit, because the compiler can't deduce it.

È anche possibile chiamare il metodo da una catena di attività arbitraria simile a quella seguente.We could also call the method from within an arbitrary task chain like this. Anche in questo caso, con un tipo restituito lambda esplicito.Again, with an explicit lambda return type.

...
.then([this]() -> concurrency::task<bool> {
    co_return co_await GetBoolMemberFunctionAsync();
}).then([this](bool result) {
    ...
});
...

Convertire un elemento winrt::IAsyncXxx<T> (T è un tipo di Windows Runtime) in un elemento task<T^>Convert a winrt::IAsyncXxx<T> (T is a Windows Runtime type) to a task<T^>

Lo schema di questa sezione si applica quando viene restituito in modo asincrono un valore di Windows Runtime (verrà usato un valore StorageFile per la descrizione).The pattern in this section applies when you're asynchronously returning a Windows Runtime value (we'll use a StorageFile value to illustrate). Si consideri un esempio in cui un metodo già convertito in C++/WinRT ha questa firma.Consider an example where a method that you've already ported to C++/WinRT has this signature.

winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::Storage::StorageFile>
MyClass::GetStorageFileMemberFunctionAsync()
{
    co_return co_await winrt::Windows::Storage::StorageFile::GetFileFromPathAsync
    (L"MyFile.txt");
}

Il listato seguente illustra come convertire una chiamata a tale metodo in un'attività.This next listing shows how to convert a call to that method into a task. Si noti che è necessario chiamare la funzione helper di interoperabilità to_cx per convertire l'oggetto C++/WinRT restituito in un oggetto handle C++/CX (definito anche hat).Notice that we need to call the to_cx interop helper function to convert the returned C++/WinRT object into a C++/CX handle (also known as a hat) object.

task<Windows::Storage::StorageFile^> RetrieveStorageFileTask()
{
    winrt::Windows::Storage::StorageFile storageFile =
        co_await GetStorageFileMemberFunctionAsync();
    co_return to_cx<Windows::Storage::StorageFile>(storageFile);
}

Di seguito è riportata una versione più sintetica.Here's a more succinct version of that.

task<Windows::Storage::StorageFile^> RetrieveStorageFileTask()
{
    co_return to_cx<Windows::Storage::StorageFile>(GetStorageFileMemberFunctionAsync());
}

È addirittura possibile scegliere di eseguire il wrapping di tale schema in un modello di funzione riutilizzabile e usare return come se si restituisse normalmente un'attività.And you can even choose to wrap that pattern up into a reusable function template, and return it just like you'd normally return a task.

template<typename ResultTypeCX, typename Awaitable>
concurrency::task<ResultTypeCX^> to_task(Awaitable awaitable)
{
    co_return to_cx<ResultTypeCX>(co_await awaitable);
}

task<Windows::Storage::StorageFile^> RetrieveStorageFileTask()
{
    return to_task<Windows::Storage::StorageFile>(GetStorageFileMemberFunctionAsync());
}

Se si preferisce questa idea, è possibile aggiungere to_task a interop_helpers.h.If you like that idea, you might want to add to_task to interop_helpers.h.

Eseguire il wrapping di create_async in un'attività che usa co_returnWrap create_async around a task that uses co_return

Non è possibile co_return un oggetto IAsyncXxx^ direttamente, ma è possibile ottenere un risultato simile.You can't co_return an IAsyncXxx^ directly, but you can achieve something similar. Nel caso di un'attività che restituisce un valore in modo cooperativo, è possibile eseguire il wrapping di tale attività all'interno di una chiamata a concurrency::create_async.If you have a task that cooperatively returns a value, then you can wrap that inside a call to concurrency::create_async.

Di seguito è riportato un esempio ipotetico, dal momento che non è disponibile un esempio che si possa estrapolare da Simple3DGameDX.Here's a hypothetical example, since there isn't an example we can lift from Simple3DGameDX.

Windows::Foundation::IAsyncOperation<bool>^ MyClass::RetrieveBoolAsync()
{
    return concurrency::create_async(
        [this]() -> concurrency::task<bool> {
            bool result = co_await GetBoolMemberFunctionAsync();
            co_return result;
        });
}

Come si può notare, è possibile ottenere il valore restituito da qualsiasi metodo su cui è possibile eseguire una coroutine co_await.As you can see, you could obtain the return value from any method that you can co_await.

Convertire concurrency::wait in co_await winrt::resume_afterPort concurrency::wait to co_await winrt::resume_after

Esistono alcune posizioni in cui Simple3DGameDX usa concurrency::wait per sospendere il thread per un breve periodo di tempo.There are a couple of places where Simple3DGameDX uses concurrency::wait to pause the thread for a short amount of time. Ecco un esempio.Here's an example.

// GameConstants.h
namespace GameConstants
{
    ...
    static const int InitialLoadingDelay = 2000;
    ...
}

// GameRenderer.cpp
task<void> GameRenderer::CreateGameDeviceResourcesAsync(_In_ Simple3DGame^ game)
{
    std::vector<task<void>> tasks;
    ...
    tasks.push_back(create_task([]()
    {
        wait(GameConstants::InitialLoadingDelay);
    }));
    ...
}

La versione di C++/WinRT di concurrency::wait è lo struct winrt::resume_after.The C++/WinRT version of concurrency::wait is the winrt::resume_after struct. È possibile co_await tale struct all'interno di un'attività della libreria PPL.We can co_await that struct inside a PPL task. Ecco un esempio di codice.Here's a code example.

// GameConstants.h
namespace GameConstants
{
    using namespace std::literals::chrono_literals;
    ...
    static const auto InitialLoadingDelay = 2000ms;
    ...
}

// GameRenderer.cpp
task<void> GameRenderer::CreateGameDeviceResourcesAsync(_In_ Simple3DGame^ game)
{
    std::vector<task<void>> tasks;
    ...
    tasks.push_back(create_task([]() -> task<void>
    {
        co_await winrt::resume_after(GameConstants::InitialLoadingDelay);
    }));
    ...
}

Si notino le altre due modifiche che è stato necessario apportare.Notice the two other changes that we had to make. È stato modificato il tipo di GameConstants::InitialLoadingDelay in std::chrono::duration ed è stato reso esplicito il tipo restituito della funzione lambda, perché il compilatore non è più in grado di dedurlo.We changed the type of GameConstants::InitialLoadingDelay to std::chrono::duration, and we made the return type of the lambda function explicit, because the compiler is no longer able to deduce it.

Convertire un tipo restituito task<void> in winrt::IAsyncXxxPort a task<void> return type to winrt::IAsyncXxx

Simple3DGame::LoadLevelAsyncSimple3DGame::LoadLevelAsync

In questa fase dell'operazione con Simple3DGameDX, tutte le posizioni nel progetto che chiamano Simple3DGame::LoadLevelAsync usano co_await per chiamarlo.At this stage in our work with Simple3DGameDX, all of the places in the project that call Simple3DGame::LoadLevelAsync use co_await to call it.

Ciò significa che è possibile semplicemente modificare il tipo restituito del metodo da task<void> a winrt::Windows::Foundation::IAsyncAction (lasciando la parte restante invariata).That means that we can simply change that method's return type from task<void> to winrt::Windows::Foundation::IAsyncAction (leaving the rest of it unchanged).

winrt::Windows::Foundation::IAsyncAction Simple3DGame::LoadLevelAsync()
{
    m_level[m_currentLevel]->Initialize(m_objects);
    m_levelDuration = m_level[m_currentLevel]->TimeLimit() + m_levelBonusTime;
    co_return co_await m_renderer->LoadLevelResourcesAsync();
}

I passaggi successivi per convertire la parte restante del metodo e le relative dipendenze (ad esempio, m_level e così via) in C++/WinRT dovrebbero essere abbastanza meccanici.It should now be fairly mechanical to port the rest of that method, and its dependencies (such as m_level, and so on), to C++/WinRT.

GameRenderer::LoadLevelResourcesAsyncGameRenderer::LoadLevelResourcesAsync

Di seguito è illustrata la versione originale di C++/CX per GameRenderer::LoadLevelResourcesAsync.Here's the original C++/CX version of GameRenderer::LoadLevelResourcesAsync.

// GameConstants.h
namespace GameConstants
{
    ...
    static const int LevelLoadingDelay = 500;
    ...
}

// GameRenderer.cpp
task<void> GameRenderer::LoadLevelResourcesAsync()
{
    m_levelResourcesLoaded = false;

    return create_task([this]()
    {
        wait(GameConstants::LevelLoadingDelay);
    });
}

Simple3DGame::LoadLevelAsync è l'unica posizione nel progetto che chiama GameRenderer::LoadLevelResourcesAsync e usa già co_await per chiamarlo.Simple3DGame::LoadLevelAsync is the only place in the project that calls GameRenderer::LoadLevelResourcesAsync, and it already uses co_await to call it.

Quindi non è più necessario che GameRenderer::LoadLevelResourcesAsync restituisca un'attività — può restituire invece un oggetto winrt::Windows::Foundation::IAsyncAction.So there's no longer any need for GameRenderer::LoadLevelResourcesAsync to return a task—it can return a winrt::Windows::Foundation::IAsyncAction instead. L'implementazione stessa è abbastanza semplice da convertire completamente il codice in C++/WinRT.And the implementation itself is simple enough to port completely to C++/WinRT. In questo modo, si esegue la stessa modifica apportata in Convertire concurrency::wait in co_await winrt::resume_afterThat involves making the same change we made in Port concurrency::wait to co_await winrt::resume_after. e non esistono dipendenze significative nel resto del progetto di cui preoccuparsi.And there are no significant dependencies on the rest of the project to worry about.

Ecco l'aspetto del metodo dopo la conversione completa in C++/WinRT.So here's how the method looks after porting it completely to C++/WinRT.

// GameConstants.h
namespace GameConstants
{
    using namespace std::literals::chrono_literals;
    ...
    static const auto LevelLoadingDelay = 500ms;
    ...
}

// GameRenderer.cpp
winrt::Windows::Foundation::IAsyncAction GameRenderer::LoadLevelResourcesAsync()
{
    m_levelResourcesLoaded = false;
    co_return co_await winrt::resume_after(GameConstants::LevelLoadingDelay);
}

Obiettivo: conversione completa di un metodo in C++/WinRTThe goal—fully port a method to C++/WinRT

È possibile concludere questa procedura dettagliata con un esempio dell'obiettivo finale, eseguendo la conversione completa del metodo BasicReaderWriter::ReadDataAsync in C++/WinRT.Let's wrap up this walkthrough with an example of the end goal, by fully porting the method BasicReaderWriter::ReadDataAsync to C++/WinRT.

L'ultima volta che il metodo è stato esaminato (nella sezione Convertire ReadDataAsync (principalmente) in C++/WinRT, lasciando invariato il resto del progetto), gran parte del codice è stata convertita in C++/WinRT.Last time we looked at this method (in the section Port ReadDataAsync (mostly) to C++/WinRT, leaving the rest of the project unchanged), it was mostly ported to C++/WinRT. Restituiva tuttavia un'attività di Platform::Array<byte> ^.But it still returned a task of Platform::Array<byte>^.

task<Platform::Array<byte>^> BasicReaderWriter::ReadDataAsync(
    _In_ Platform::String^ filename)
{
    auto location_from_cx = from_cx<winrt::Windows::Storage::StorageFolder>(m_location);

    auto file = co_await location_from_cx.GetFileAsync(filename->Data());
    auto buffer = co_await winrt::Windows::Storage::FileIO::ReadBufferAsync(file);
    byte* bytes;
    auto byteAccess = buffer.as<Windows::Storage::Streams::IBufferByteAccess>();
    winrt::check_hresult(byteAccess->Buffer(&bytes));

    co_return ref new Platform::Array<byte>(bytes, buffer.Length());
}

Anziché restituire un'attività, il metodo verrà modificato in modo da restituire IAsyncOperation.Instead of returning a task, we'll change it to return an IAsyncOperation. Inoltre, anziché restituire una matrice di byte tramite IAsyncOperation, verrà restituito un oggetto IBuffer di C++/WinRT.And instead of returning an array of bytes via that IAsyncOperation, we'll instead return a C++/WinRT IBuffer object. Come si vedrà, questa operazione richiede anche una lieve modifica al codice nei siti di chiamata.That will also require a minor change to the code at the call sites, as we'll see.

Ecco l'aspetto del metodo dopo la conversione dell'implementazione, del parametro e del membro dati m_location per l'uso della sintassi e degli oggetti di C++/WinRT.Here's how the method looks after porting its implementation, its parameter, and the m_location data member to use C++/WinRT syntax and objects.

winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::Storage::Streams::IBuffer>
BasicReaderWriter::ReadDataAsync(
    _In_ winrt::hstring const& filename)
{
    StorageFile file{ co_await m_location.GetFileAsync(filename) };
    co_return co_await FileIO::ReadBufferAsync(file);
}

winrt::array_view<byte> BasicLoader::GetBufferView(
    winrt::Windows::Storage::Streams::IBuffer const& buffer)
{
    byte* bytes;
    auto byteAccess = buffer.as<Windows::Storage::Streams::IBufferByteAccess>();
    winrt::check_hresult(byteAccess->Buffer(&bytes));
    return { bytes, bytes + buffer.Length() };
}

Come si può notare, l'oggetto BasicReaderWriter::ReadDataAsync è molto più semplice, perché è stato incluso nel metodo della logica sincrona che recupera i byte dal buffer.As you can see, BasicReaderWriter::ReadDataAsync itself is much simpler, because we've factored into its own method the synchronous logic that retrieves bytes from the buffer.

A questo punto, è necessario convertire i siti di chiamata da questo tipo di struttura in C++/CX:But now we need to port the call sites from this kind of structure in C++/CX.

task<void> BasicLoader::LoadTextureAsync(...)
{
    return m_basicReaderWriter->ReadDataAsync(filename).then(
        [=](const Platform::Array<byte>^ textureData)
    {
        CreateTexture(...);
    });
}

A questo modello in C++/WinRT:To this pattern in C++/WinRT.

winrt::Windows::Foundation::IAsyncAction BasicLoader::LoadTextureAsync(...)
{
    auto textureBuffer = co_await m_basicReaderWriter.ReadDataAsync(filename);
    auto textureData = GetBufferView(textureBuffer);
    CreateTexture(...);
}

API importantiImportant APIs