Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Tip
Obwohl empfohlen wird, dieses Thema von Anfang an zu lesen, können Sie direkt zu einer Zusammenfassung der Interoptechniken im Abschnitt "Übersicht über das Portieren von C++/CX async zu C++/WinRT " springen.
Dies ist ein erweitertes Thema im Zusammenhang mit der schrittweisen Portierung zu C++/WinRT von C++/CX. Dieses Thema knüpft dort an, wo das Thema Interop zwischen C++/WinRT und C++/CX endet.
Wenn die Größe oder Komplexität Ihrer Codebasis es erforderlich macht, Ihr Projekt schrittweise zu portieren, benötigen Sie einen Portierungsprozess, in dem für einen Zeitraum C++/CX- und C++/WinRT-Code nebeneinander im selben Projekt vorhanden ist. Wenn Sie asynchronen Code haben, müssen Sie möglicherweise Über Parallel Patterns Library (PPL)-Taskchains verfügen, und Coroutines sind in Ihrem Projekt nebeneinander vorhanden, während Sie den Quellcode schrittweise portieren. In diesem Thema geht es um Techniken für die Interoperabilität zwischen asynchronen C++/CX-Code und asynchronen C++/WinRT-Code. Sie können diese Techniken einzeln oder zusammen verwenden. Mit den Techniken können Sie schrittweise, kontrollierte, lokale Änderungen entlang des Pfads zum Portieren des gesamten Projekts vornehmen, ohne dass jede Änderung im gesamten Projekt unkontrolliert überlappend erfolgt.
Bevor Sie dieses Thema lesen, ist es ratsam, die Interoperabilität zwischen C++/WinRT und C++/CX zu lesen. In diesem Thema erfahren Sie, wie Sie Ihr Projekt für die schrittweise Portierung vorbereiten. Außerdem werden zwei Hilfsfunktionen eingeführt, mit denen Sie ein C++/CX-Objekt in ein C++/WinRT-Objekt konvertieren können (und umgekehrt). Dieses Thema zur Asynchronität baut auf diesen Informationen auf und verwendet diese Hilfsfunktionen.
Hinweis
Es gibt einige Einschränkungen für die schrittweise Portierung von C++/CX zu C++/WinRT. Wenn Sie über ein Windows-Runtime Komponentenprojekt verfügen, ist das Portieren nach und nach nicht möglich, und Sie müssen das Projekt in einem Durchgang portieren. Und für ein XAML-Projekt müssen Ihre XAML-Seitentypen entweder alle C++/WinRT oder alle C++/CX sein. Weitere Informationen finden Sie im Thema "Wechseln zu C++/WinRT" aus C++/CX.
Der Grund, warum ein gesamtes Thema der asynchronen Code-Interoperabilität gewidmet ist
Das Portieren von C++/CX zu C++/WinRT ist in der Regel einfach, mit ausnahme der Umstellung von Parallel Patterns Library (PPL) -Aufgaben auf Coroutines. Die Modelle unterscheiden sich. Es gibt keine natürliche 1:1-Zuordnung von PPL-Aufgaben zu Coroutinen, und es gibt keine einfache Methode (die für alle Fälle funktioniert), um den Code mechanisch zu portieren.
Die gute Nachricht ist, dass die Umwandlung von Vorgängen zu Coroutinen zu erheblichen Vereinfachungen führt. Und Entwicklungsteams berichten routinemäßig, dass der Rest der Portierungsarbeiten weitgehend mechanisch ist, sobald sie die Hürde der Portierung ihres asynchronen Codes überschreiten.
Häufig wurde ein Algorithmus ursprünglich für synchrone APIs geschrieben. Und das dann in Vorgänge und explizite Fortsetzungen übersetzt wurde – das Ergebnis ist oft eine versehentliche Verschleierung der zugrunde liegenden Logik. Beispielsweise werden Schleifen zu Rekursion; If-else-Zweige werden zu einem verschachtelten Baum (einer Kette) aus Aufgaben; gemeinsam genutzte Variablen werden zu shared_ptr. Um die häufig unnatürliche Struktur des PPL-Quellcodes zu dekonstruieren, empfehlen wir Ihnen, zuerst einen Schritt zurück zu machen und die Absicht des ursprünglichen Codes zu verstehen (d. h. die ursprüngliche synchrone Version zu ermitteln). Und fügen Sie co_await dann (kooperativ warten) an die entsprechenden Stellen ein.
Aus diesem Grund kann es Ihnen die Arbeit erleichtern und zu einer saubereren Portierung führen, wenn Sie zu Beginn Ihrer Portierung eine C#-Version (anstelle von C++/CX) des asynchronen Codes zur Verfügung haben. C#-Code verwendet await. Daher folgt C#-Code bereits im Wesentlichen einer Philosophie, mit einer synchronen Version zu beginnen und dann an die entsprechenden Stellen einzufügen await .
Wenn Sie nicht über eine C#-Version Ihres Projekts verfügen, können Sie die in diesem Thema beschriebenen Techniken verwenden. Und nachdem Sie zu C++/WinRT portiert haben, ist die Struktur Ihres asynchronen Codes dann einfacher zu C# zu portieren, wenn Sie möchten.
Einige Grundlagen der asynchronen Programmierung
Damit wir bei asynchronen Programmierkonzepten und der zugehörigen Terminologie einen gemeinsamen Bezugsrahmen haben, skizzieren wir kurz den allgemeinen Hintergrund der asynchronen Programmierung mit der Windows-Runtime und auch, wie die beiden C++-Sprachprojektionen jeweils auf unterschiedliche Weise darauf aufbauen.
Ihr Projekt verfügt über Methoden, die asynchron funktionieren, und es gibt zwei Haupttypen.
- Es ist üblich, dass Sie auf den Abschluss der asynchronen Arbeit warten möchten, bevor Sie etwas anderes ausführen. Eine Methode, die ein asynchrones Vorgangsobjekt zurückgibt, ist eines, auf das Sie warten können.
- Manchmal möchten oder müssen Sie jedoch nicht auf den Abschluss der asynchronen Arbeit warten. In diesem Fall ist es effizienter, dass die asynchrone Methode kein asynchrones Vorgangsobjekt zurückgibt. Eine asynchrone Methode, z. B. eine Methode, auf die Sie nicht warten, wird als Fire-and-Forget-Methode bezeichnet.
Asynchrone Windows-Runtime-Objekte (IAsyncXxx)
Der namespace Windows::Foundation Windows-Runtime enthält vier Typen von asynchronen Vorgangsobjekten.
- IAsyncAction,
- IAsyncActionWithProgress<TProgress>,
- IAsyncOperation<TResult> und
- IAsyncOperationWithProgress<TResult, TProgress>.
Wenn wir in diesem Thema die praktische Kurzform von IAsyncXxx verwenden, verweisen wir auf diese Typen zusammen; oder wir sprechen über einen der vier Typen, ohne angeben zu müssen, welche.
C++/CX async
Asynchroner C++/CX-Code verwendet PPL-Aufgaben (Parallel Patterns Library). Eine PPL-Aufgabe wird durch die Klasse concurrency::task dargestellt.
In der Regel verknüpft eine asynchrone C++/CX-Methode PPL-Tasks mithilfe von Lambda-Funktionen mit concurrency::create_task und concurrency::task::then. Jede Lambda-Funktion gibt einen Vorgang zurück, der nach Abschluss des Vorgangs einen Wert erzeugt, der dann an die Lambda-Funktion der Fortsetzung des Vorgangs übergeben wird.
Alternativ kann eine asynchrone C++/CX-Methode statt create_task zum Erstellen einer Aufgabe concurrency::create_async aufrufen, um ein IAsyncXxx^-Objekt zu erstellen.
Der Rückgabetyp einer asynchronen C++/CX-Methode kann also ein PPL-Task oder ein IAsyncXxx^ sein.
In beiden Fällen verwendet die Methode selbst das return Schlüsselwort, um ein asynchrones Objekt zurückzugeben, das nach Abschluss des Vorgangs den Wert erzeugt, den der Aufrufer tatsächlich wünscht (z. B. eine Datei, ein Bytearray oder ein boolean).
Hinweis
Wenn eine asynchrone C++/CX-Methode einen IAsyncXxx^-Wert zurückgibt, ist das TResult (falls vorhanden) auf einen Windows-Runtime Typ beschränkt. Ein boolescher Wert ist beispielsweise ein Windows-Runtime-Typ; ein projizierter C++/CX-Typ (beispielsweise Platform::Array<byte>^) jedoch nicht.
C++/WinRT async
C++/WinRT integriert C++-Coroutinen in das Programmiermodell. Coroutines und die co_await Aussage bieten eine natürliche Möglichkeit, kooperativ auf ein Ergebnis zu warten.
Jeder der IAsyncXxx-Typen wird in einen entsprechenden Typ im winrt::Windows::Foundation C++/WinRT-Namespace projiziert. Betrachten wir diese als winrt::IAsyncXxx (im Vergleich zum IAsyncXxx^ von C++/CX).
Der Rückgabetyp einer C++/WinRT-Koroutine ist entweder ein winrt::IAsyncXxx, oder winrt::fire_and_forget. Und anstatt das Schlüsselwort return zu verwenden, um ein asynchrones Objekt zurückzugeben, verwendet eine Coroutine das Schlüsselwort co_return, um den Wert kooperativ zurückzugeben, den der Aufrufer tatsächlich haben möchte (vielleicht eine Datei, ein Byte-Array oder einen Booleschen Wert).
Wenn eine Methode mindestens eine co_await Anweisung (oder mindestens eine co_return oder co_yield) enthält, ist die Methode aus diesem Grund ein Coroutine.
Weitere Informationen und Codebeispiele finden Sie unter Parallelität und asynchrone Vorgänge mit C++/WinRT.
Das Direct3D-Spielbeispiel (Simple3DGameDX)
Dieses Thema enthält exemplarische Vorgehensweisen verschiedener spezifischer Programmiertechniken, die veranschaulichen, wie asynchroner Code schrittweise portiert wird. Um als Fallstudie zu dienen, verwenden wir die C++/CX-Version des Direct3D-Beispielspiels (das als Simple3DGameDX bezeichnet wird). Wir zeigen einige Beispiele dafür, wie Sie den ursprünglichen C++/CX-Quellcode in diesem Projekt verwenden und den asynchronen Code schrittweise zu C++/WinRT portieren können.
- Laden Sie die ZIP-Datei über den obigen Link herunter, und entpacken Sie sie.
- Öffnen Sie das C++/CX-Projekt (im Ordner mit dem Namen
cpp) in Visual Studio. - Anschließend müssen Sie dem Projekt C++/WinRT-Unterstützung hinzufügen. Die schritte, die Sie ausführen, werden unter Erstellen eines C++/CX-Projekts und Hinzufügen der C++/WinRT-Unterstützung beschrieben. In diesem Abschnitt ist der Schritt zum Hinzufügen der
interop_helpers.hHeaderdatei zu Ihrem Projekt besonders wichtig, da wir abhängig von diesen Hilfsfunktionen in diesem Thema sind. - Fügen Sie schließlich
#include <pplawait.h>zupch.hhinzu. Dadurch erhalten Sie Coroutine-Unterstützung für PPL (weitere Informationen zu dieser Unterstützung finden Sie im folgenden Abschnitt).
Führen Sie den Build noch nicht aus, andernfalls erhalten Sie Fehlermeldungen, dass byte mehrdeutig ist. Hier erfahren Sie, wie Sie dies beheben.
- Öffnen Sie
BasicLoader.cpp, und kommentieren Sieusing namespace std;aus. - In derselben Quellcodedatei müssen Sie dann shared_ptr als "std::shared_ptr" qualifizieren. Sie können dies mit einer Suche und Ersetzung innerhalb dieser Datei tun.
- Qualifizieren Sie dann den Vektor als "std::vector" und " string " als "std::string".
Das Projekt lässt sich jetzt wieder erstellen, unterstützt C++/WinRT und enthält die Interop-Hilfsfunktionen from_cx und to_cx.
Sie haben nun das Simple3DGameDX-Projekt bereit, um den Codewalkthroughs in diesem Thema zu folgen.
Übersicht über das Portieren von C++/CX async zu C++/WinRT
Kurz gesagt werden wir bei der Portierung PPL-Task-Ketten in Aufrufe an co_await umwandeln. Wir ändern den Rückgabewert einer Methode von einer PPL-Aufgabe in ein C++/WinRT winrt::IAsyncXxx-Objekt . Außerdem ersetzen wir jedes IAsyncXxx^ durch ein C++/WinRT-winrt::IAsyncXxx.
Sie erinnern sich, dass eine Coroutine jede Methode ist, die co_xxx aufruft. Eine C++/WinRT-Coroutine verwendet co_return, um seinen Wert auf kooperative Weise zurückzugeben. Dank der Coroutine-Unterstützung für PPL (dank pplawait.h) können Sie auch co_return verwenden, um aus einer Coroutine eine PPL-Task zurückzugeben. Und Sie können auch co_await sowohl Tasks als auch IAsyncXxx verwenden. Sie können jedoch nicht verwenden co_return , um ein IAsyncXxx^-Element zurückzugeben. Die folgende Tabelle beschreibt die Unterstützung der Interoperabilität zwischen den verschiedenen asynchronen Techniken unter Einbeziehung von pplawait.h.
| Methode | Können Sie co_await dies tun? |
Können Sie co_return davon profitieren? |
|---|---|---|
| Die Methode gibt task<void> zurück. | Yes | Yes |
| Die Methode gibt Vorgang T< zurück.> | No | Yes |
| Methode gibt IAsyncXxx zurück.^ | Yes | No. Aber Sie verwenden create_async um eine Aufgabe herum, die co_return verwendet. |
| Die Methode gibt winrt::IAsyncXxx zurück. | Yes | Yes |
Verwenden Sie diese nächste Tabelle, um direkt zum Abschnitt in diesem Thema zu springen, der eine Interoptechnik von Interesse beschreibt, oder fahren Sie einfach mit dem Lesen von hier fort.
| Async Interop-Technik | Abschnitt zu diesem Thema |
|---|---|
Verwenden Sie co_await, um innerhalb einer Fire-and-Forget-Methode oder eines Konstruktors auf eine Task<void>-Methode zu warten. |
Await <task>void innerhalb einer Fire-and-Forget-Methode |
Verwenden Sie co_await, um aus einer task<void>-Methode eine task<void>-Methode zu erwarten. |
Await <task>void innerhalb einer task<void>-Methode |
Verwenden Sie co_await, um innerhalb einer task<T>-Methode auf eine task<void>-Methode zu warten. |
Await <task>void innerhalb einer <T-Methode |
Verwenden Sie co_await, um auf eine IAsyncXxx^-Methode zu warten. |
Warten Sie auf ein IAsyncXxx^-Element in einer Vorgangsmethode , sodass der Rest des Projekts unverändert bleibt. |
Verwenden Sie co_return innerhalb einer task<void>-Methode. |
Await <task>void in einer task<void>-Methode |
Verwenden Sie co_return innerhalb einer task<T>-Methode. |
Warten Sie auf ein IAsyncXxx^-Element in einer Vorgangsmethode , sodass der Rest des Projekts unverändert bleibt. |
Lege create_async um eine Aufgabe, die co_return verwendet. |
create_async um eine Aufgabe legen, die co_return verwendet |
| Portieren Sie concurrency::wait. |
Portiere concurrency::wait nach co_await winrt::resume_after |
| Geben Sie winrt::IAsyncXxx anstelle von task<void> zurück. | Einen task<void-Rückgabetyp> in winrt::IAsyncXxx portieren |
| Konvertieren Sie ein winrt::IAsyncXxx<T> (T ist ein Grundtyp) in ein task<T>. | Konvertieren Sie ein winrt::IAsyncXxx<T> (T ist ein primitiver Typ) in ein task<T> |
| Konvertieren Sie ein winrt::IAsyncXxx<T> (T ist ein Windows-Runtime-Typ) zu einem task<T^>. | Ein winrt::IAsyncXxx<T> (T ist ein Windows-Runtime-Typ) in ein task<T^> konvertieren |
Und hier ist ein kurzes Codebeispiel, das einige der Unterstützung veranschaulicht.
#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();
}
Important
Auch bei diesen großartigen Interop-Optionen hängt die Portierung schrittweise von der Auswahl von Änderungen ab, die wir operativ vornehmen können, die sich nicht auf den Rest des Projekts auswirken. Wir wollen vermeiden, an irgendeinem losen Faden zu ziehen und damit die Struktur des gesamten Projekts aufzulösen. Dafür müssen wir dinge in einer bestimmten Reihenfolge tun. Als Nächstes befassen wir uns mit einigen Beispielen, wie diese Arten von asynchronen Portierungs-/Interopänderungen vorgenommen werden.
Warten Sie auf eine Vorgangs<void-Methode> , und lassen Sie den Rest des Projekts unverändert.
Eine Methode, die "task<void"> zurückgibt, führt asynchron Arbeit durch und gibt ein asynchrones Vorgangsobjekt zurück, erzeugt aber letztendlich keinen Wert. Wir können co_await eine solche Methode.
Daher ist ein guter Ausgangspunkt für die schrittweise Portierung von asynchronem Code die Suche nach Orten, an denen Sie solche Methoden aufrufen. Diese Orte umfassen das Erstellen und/oder Zurückgeben einer Aufgabe. Sie können auch die Art der Aufgabenkette umfassen, bei der kein Wert von jedem Vorgang an die Fortsetzung übergeben wird. An stellen wie diesem können Sie einfach den asynchronen Code durch co_await Anweisungen ersetzen, wie wir sehen.
Hinweis
Im weiteren Verlauf dieses Themas werden Sie die Vorteile dieser Strategie erkennen. Sobald eine bestimmte Task<void-Methode> ausschließlich über co_awaitaufgerufen wird, können Sie diese Methode zu C++/WinRT portieren und ein winrt::IAsyncXxx zurückgeben.
Lassen Sie uns einige Beispiele finden. Öffnen Sie das Simple3DGameDX-Projekt (siehe Direct3D-Spielbeispiel).
Important
Behalten Sie in den folgenden Beispielen, wenn Sie sich die geänderten Implementierungen von Methoden ansehen, im Hinterkopf, dass wir die callers der Methoden, die wir ändern, nicht ändern müssen. Diese Änderungen sind lokal begrenzt und wirken sich nicht auf das gesamte Projekt aus.
Auf task<void> innerhalb einer Fire-and-Forget-Methode warten
Beginnen wir mit dem Awaiten von task<void> innerhalb von Fire-and-Forget-Methoden, da dies der einfachste Fall ist. Dies sind Methoden, die asynchron funktionieren, aber der Aufrufer der Methode wartet nicht, bis diese Arbeit abgeschlossen ist. Sie rufen einfach die Methode auf und vergessen sie, obwohl sie asynchron abgeschlossen ist.
Suchen Sie im Wurzelbereich des Abhängigkeitsdiagramms Ihres Projekts nach voidMethoden, die create_task enthalten, und/oder nach Aufgabenketten, in denen nur task<void>-Methoden aufgerufen werden.
In Simple3DGameDX finden Sie ähnlichen Code in der Implementierung der Methode GameMain::Update. Sie befindet sich in der Quellcodedatei GameMain.cpp.
GameMain::Update
Hier ist ein Extrakt aus der C++/CX-Version der Methode, die die beiden Teile der Methode anzeigt, die asynchron abgeschlossen sind.
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());
...
...
}
Sie sehen einen Aufruf der Simple3DGame::LoadLevelAsync-Methode (die eine PPL-Aufgabe<void> zurückgibt). Darauf folgt eine Continuation, die synchronen Code ausführt. LoadLevelAsync ist asynchron, gibt jedoch keinen Wert zurück. Daher wird kein Wert von der Aufgabe an die Fortsetzung übergeben.
Wir können an diesen beiden Stellen dieselbe Art von Änderung am Code vornehmen. Der Code wird nach der nachstehenden Auflistung erläutert. Wir könnten hier darüber diskutieren, wie man in einer Klassenmember-Koroutine sicher auf den this-Zeiger zugreift. Aber lassen Sie uns dies für einen späteren Abschnitt (Die verzögerte Diskussion über co_await und diesen Zeiger) zurückstellen – für den Moment funktioniert dieser Code.
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;
...
...
}
Wie Sie sehen können, da LoadLevelAsync eine Aufgabe zurückgibt, können co_await wir dies. Und wir benötigen keine explizite Fortsetzung – der Code, der auf ein co_await folgt, wird erst ausgeführt, wenn LoadLevelAsync abgeschlossen ist.
Durch das Hinzufügen von co_await wird die Methode zu einer Coroutine, sodass wir nicht zulassen konnten, dass sie weiterhin void zurückgibt. Es ist eine Fire-and-forget-Methode, daher haben wir sie so geändert, dass winrt::fire_and_forget zurückgegeben wird.
Sie müssen außerdem GameMain.h bearbeiten. Ändern Sie auch dort in der Deklaration den Rückgabetyp von GameMain::Update von void zu winrt::fire_and_forget.
Sie können diese Änderung an Ihrer Kopie des Projekts vornehmen, und das Spiel wird weiterhin erstellt und ausgeführt. Der Quellcode ist immer noch grundsätzlich C++/CX, verwendet aber jetzt dieselben Muster wie C++/WinRT, sodass wir uns etwas näher gebracht haben, um den Rest des Codes mechanisch portieren zu können.
GameMain::ResetGame
GameMain::ResetGame ist eine weitere Fire-and-Forget-Methode; auch sie ruft LoadLevelAsync auf. Sie können also dort dieselbe Codeänderung vornehmen, wenn Sie die Übung wünschen.
GameMain::OnDeviceRestored
In GameMain::OnDeviceRestored wird es aufgrund der tieferen Schachtelung von Async-Code, einschließlich einer No-Op-Task, etwas interessanter. Hier ist ein Überblick über die asynchronen Teile der Methode (wobei der weniger interessante synchrone Code durch Auslassungspunkte angedeutet wird).
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());
}
Ändern Sie zunächst den Rückgabetyp von GameMain::OnDeviceRestored von void in winrt::fire_and_forget in GameMain.h und .cpp. Sie müssen auch DeviceResources.h öffnen und dieselbe Änderung am Rückgabetyp von IDeviceNotify::OnDeviceRestored vornehmen.
Um den asynchronen Code zu portieren, entfernen Sie alle Aufrufe von create_task und then sowie die dazugehörigen geschweiften Klammern, und vereinfachen Sie die Methode zu einer flachen Folge von Anweisungen.
Ändern Sie alle return, die eine Task zurückgeben, in eine co_await. Sie bleiben bei einem return , der nichts zurückgibt, also löschen Sie das einfach. Wenn Sie fertig sind, wird der No-Op-Task verschwunden sein, und die Grundstruktur der asynchronen Teile der Methode wird wie folgt aussehen. Auch hier wird der weniger interessante synchrone Code entfernt.
winrt::fire_and_forget GameMain::OnDeviceRestored()
{
...
co_await m_renderer->CreateGameDeviceResourcesAsync(m_game);
...
if (m_updateState == UpdateEngineState::WaitingForResources)
{
co_await m_game->LoadLevelAsync();
...
}
...
}
Wie Sie sehen können, ist diese Form der asynchronen Struktur wesentlich einfacher und einfacher zu lesen.
GameMain::GameMain
Der GameMain::GameMain-Konstruktor führt asynchrone Arbeit durch, und kein Teil des Projekts wartet auf den Abschluss dieser Arbeit. Auch hier werden die asynchronen Teile in dieser Auflistung beschrieben.
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());
}
Aber ein Konstruktor kann "winrt::fire_and_forget" nicht zurückgeben. Daher verschieben wir den asynchronen Code in eine neue GameMain::ConstructInBackground-Fire-and-Forget-Methode , flachten den Code in co_await Anweisungen und rufen die neue Methode aus dem Konstruktor auf. Hier sehen Sie das Ergebnis.
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();
...
}
...
}
Jetzt wurden alle Fire-and-Forget-Methoden – genauer gesagt der gesamte asynchrone Code – in GameMain in Koroutinen umgewandelt. Falls Sie möchten, könnten Sie in anderen Klassen nach Methoden nach dem Fire-and-Forget-Prinzip suchen und ähnliche Änderungen vornehmen.
Die verschobene Diskussion über co_await und den this-Pointer
Als wir Änderungen an GameMain::Update vorgenommen haben, habe ich eine Diskussion über diesen Zeiger verzögert. Lassen Sie uns diese Diskussion hier führen.
Dies gilt für alle Methoden, die wir bislang geändert haben; und das gilt für alle Coroutinen, nicht nur für Fire-and-Forget-Coroutinen. Das Einfügen von co_await in eine Methode führt zu einem Unterbrechungspunkt. Und deshalb müssen wir mit diesem Zeiger vorsichtig sein, den wir natürlich bei jedem Zugriff auf ein Klassenmitglied nach dem Anhaltepunkt nutzen.
Kurz gesagt besteht die Lösung darin, implements::get_strong aufzurufen. Für eine vollständige Erörterung des Problems und der Lösung siehe Sicherer Zugriff auf den this-Zeiger in einer Koroutine einer Klassenmemberfunktion.
Sie können implements::get_strong nur in einer Klasse aufrufen, die von winrt::implements abgeleitet wird.
GameMain von winrt::implements ableiten
Die erste Änderung, die wir vornehmen müssen, ist in GameMain.h.
class GameMain :
public DX::IDeviceNotify
GameMain wird weiterhin DX::IDeviceNotify implementieren, aber wir werden es ändern, um von winrt::implements abzuleiten.
class GameMain :
public winrt::implements<GameMain, winrt::Windows::Foundation::IInspectable>,
DX::IDeviceNotify
Als Nächstes finden Sie in App.cpp diese Methode.
void App::Load(Platform::String^)
{
if (!m_main)
{
m_main = std::unique_ptr<GameMain>(new GameMain(m_deviceResources));
}
}
Da GameMain jedoch von winrt::implements abgeleitet ist, müssen wir es auf andere Weise konstruieren. In diesem Fall verwenden wir die Funktionsvorlage "winrt::make_self ". Weitere Informationen finden Sie unter Instanziieren und Zurückgeben von Implementierungstypen und Schnittstellen.
Ersetzen Sie diese Codezeile durch diese Zeile.
...
m_main = winrt::make_self<GameMain>(m_deviceResources);
...
Um diese Änderung abzuschließen, müssen wir auch den Typ von m_main ändern. In App.h finden Sie diesen Code.
ref class App sealed :
public Windows::ApplicationModel::Core::IFrameworkView
{
...
private:
...
std::unique_ptr<GameMain> m_main;
};
Ändern Sie die Deklaration von m_main in diese.
...
winrt::com_ptr<GameMain> m_main;
...
Wir können jetzt implementieren::get_strong
Für GameMain::Update und für alle anderen Methoden, denen wir ein co_await hinzugefügt haben, können Sie get_strong zu Beginn einer Coroutine wie folgt aufrufen, um sicherzustellen, dass eine starke Referenz bis zum Abschluss der Coroutine erhalten bleibt.
winrt::fire_and_forget GameMain::Update()
{
auto strong_this{ get_strong() }; // Keep *this* alive.
...
co_await ...
...
}
task<void> in einer task<void>-Methode erwarten
Der nächsteinfachste Fall ist das Abwarten von task<void> in einer Methode, die selbst task<void> zurückgibt. Das liegt daran, dass wir eine co_awaitTask<-Void> haben können, und wir können co_returnaus einer solchen zurückkehren.
Sie finden ein sehr einfaches Beispiel in der Implementierung der Methode Simple3DGame::LoadLevelAsync. Sie befindet sich in der Quellcodedatei 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();
}
Es gibt nur einen synchronen Code, gefolgt von der Rückgabe der Aufgabe, die von GameRenderer::LoadLevelResourcesAsync erstellt wird.
Anstatt diese Aufgabe zurückzugeben, co_await wir sie und dann co_return die resultierende 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();
}
Das sieht nicht wie eine tiefgreifende Veränderung aus. Jetzt aber, da wir GameRenderer::LoadLevelResourcesAsync über co_awaitaufrufen, können wir es portieren, um ein winrt::IAsyncXxx anstelle einer Aufgabe zurückzugeben. Darauf kommen wir später im Abschnitt Portieren eines task<void>-Rückgabetyps zu winrt::IAsyncXxx zurück.
task<void> innerhalb einer task<T>-Methode awaiten
Obwohl es keine geeigneten Beispiele gibt, die in Simple3DGameDX zu finden sind, können wir ein hypothetisches Beispiel erstellen, um das Muster zu zeigen.
Die erste Zeile im folgenden Codebeispiel veranschaulicht die einfache co_await Aufgabe void<>. Um den Rückgabetyp der Aufgabe<T> zu erfüllen, müssen wir dann asynchron eine StorageFile^-Datei zurückgeben.
co_await Dazu wird eine Windows-Runtime-API und co_return die resultierende Datei verwendet.
task<StorageFile^> Simple3DGame::LoadLevelAndRetrieveFileAsync(
StorageFolder^ location,
Platform::String^ filename)
{
co_await m_renderer->LoadLevelResourcesAsync();
co_return co_await location->GetFileAsync(filename);
}
Wir könnten auf diese Weise sogar weitere Teile der Methode zu C++/WinRT portieren.
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);
}
Das Datenmember m_renderer ist in diesem Beispiel noch immer in C++/CX.
Warten Sie auf ein IAsyncXxx^-Element in einer Vorgangsmethode , sodass der Rest des Projekts unverändert bleibt.
Wir haben gesehen, wie Sie > ausführen können. Sie können auch co_await eine Methode, die ein IAsyncXxx zurückgibt, unabhängig davon, ob es sich um eine Methode in Ihrem Projekt handelt, oder eine asynchrone Windows-API (z. B. StorageFolder.GetFileAsync, die wir im vorherigen Abschnitt kooperativ erwartet haben).
Als Beispiel dafür, wo wir diese Art von Codeänderung vornehmen können, sehen wir uns BasicReaderWriter::ReadDataAsync an (die Implementierung finden Sie in BasicReaderWriter.cpp).
Hier ist die ursprüngliche 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;
});
}
Das folgende Codelisting zeigt, dass wir co_await Windows-APIs können, die IAsyncXxx^ zurückgeben. Nicht nur das, wir können auch co_return den Wert, den BasicReaderWriter::ReadDataAsync asynchron zurückgibt (in diesem Fall ein Byte-Array). In diesem ersten Schritt wird gezeigt, wie nur diese Änderungen vorgenommen werden; wir portieren den C++/CX-Code tatsächlich in C++/WinRT im nächsten Abschnitt.
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;
}
Auch hier müssen wir die Aufrufer der von uns geänderten Methoden nicht ändern, da der Rückgabetyp nicht geändert wurde.
Portieren von ReadDataAsync (hauptsächlich) zu C++/WinRT, sodass der Rest des Projekts unverändert bleibt.
Wir können einen Schritt weiter gehen und die Methode fast vollständig nach C++/WinRT portieren, ohne einen anderen Teil des Projekts ändern zu müssen.
Die einzige Abhängigkeit, die diese Methode für den Rest des Projekts hat, ist der BasicReaderWriter:: m_location-Datenmemm, der ein C++/CX StorageFolder^-Element ist. Damit dieses Datenelement unverändert bleibt und der Parametertyp und Rückgabetyp unverändert bleiben, müssen nur einige Konvertierungen ausgeführt werden – eine am Anfang der Methode und eine am Ende. Dazu können wir die from_cx- und to_cx-Interop-Hilfsfunktionen verwenden.
So sieht BasicReaderWriter::ReadDataAsync aus, nachdem dessen Implementierung größtenteils auf C++/WinRT portiert wurde. Dies ist ein gutes Beispiel für die schrittweise Portierung. Und diese Methode befindet sich in der Phase, in der wir nicht mehr als C++/CX-Methode denken können, die einige C++/WinRT-Techniken verwendet, und sie als C++/WinRT-Methode betrachten, die mit C++/CX interagiert.
#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());
}
Hinweis
In ReadDataAsync oben erstellen und zurückgeben wir ein neues C++/CX-Array. Und natürlich tun wir dies, um den Rückgabetyp der Methode zu erfüllen (sodass wir den Rest des Projekts nicht ändern müssen).
Möglicherweise kommen Sie in Ihrem eigenen Projekt auf andere Beispiele zu, bei denen Sie nach dem Portieren das Ende der Methode erreicht haben und alles, was Sie haben, ein C++/WinRT-Objekt ist.
co_return Rufen Sie dazu einfach to_cx auf, um sie zu konvertieren. Es gibt weitere Informationen dazu und ein Beispiel für den nächsten Abschnitt.
Konvertieren eines winrt::IAsyncXxx<T> in eine Aufgabe<T>
In diesem Abschnitt wird die Situation behandelt, in der Sie eine asynchrone Methode zu C++/WinRT portiert haben (sodass sie ein winrt::IAsyncXxx<T> zurückgibt), aber Sie haben immer noch C++/CX-Code, der diese Methode aufruft, als ob sie noch eine Aufgabe zurückgibt.
- In einem Fall ist T grundtyp, der keine Konvertierung benötigt.
- Der andere Fall ist der, dass T ein Windows-Runtime-Typ ist; in diesem Fall müssen Sie diesen zu einem T^ konvertieren.
Konvertieren von winrt::IAsyncXxx<T> (T ist ein primitiver Typ) in task<T>
Das Muster in diesem Abschnitt gilt, wenn Sie asynchron einen Grundtypwert zurückgeben (zum Veranschaulichen verwenden wir einen booleschen Wert). Betrachten Sie ein Beispiel, in dem eine Methode, die Sie bereits zu C++/WinRT portiert haben, diese Signatur aufweist.
winrt::Windows::Foundation::IAsyncOperation<bool>
MyClass::GetBoolMemberFunctionAsync()
{
bool value = ...
co_return value;
}
Sie können einen Aufruf dieser Methode in eine aufgabe wie diese konvertieren.
task<bool> MyClass::RetrieveBoolTask()
{
co_return co_await GetBoolMemberFunctionAsync();
}
Oder so.
task<bool> MyClass::RetrieveBoolTask()
{
return concurrency::create_task(
[this]() -> concurrency::task<bool> {
auto result = co_await GetBoolMemberFunctionAsync();
co_return result;
});
}
Beachten Sie, dass der Rückgabetyp der task-Lambda-Funktion explizit ist, da der Compiler ihn nicht ableiten kann.
Wir könnten die Methode auch aus einer beliebigen Aufgabenkette wie diesem aufrufen. Erneut mit einem expliziten Lambda-Rückgabetyp.
...
.then([this]() -> concurrency::task<bool> {
co_return co_await GetBoolMemberFunctionAsync();
}).then([this](bool result) {
...
});
...
Umwandeln eines winrt::IAsyncXxx<T> (T ist ein Windows-Runtime-Typ) in ein task<T^>
Das Muster in diesem Abschnitt gilt, wenn Sie asynchron einen Windows-Runtime Wert zurückgeben (wir verwenden einen StorageFile-Wert zur Veranschaulichung). Betrachten Sie ein Beispiel, in dem eine Methode, die Sie bereits zu C++/WinRT portiert haben, diese Signatur aufweist.
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::Storage::StorageFile>
MyClass::GetStorageFileMemberFunctionAsync()
{
co_return co_await winrt::Windows::Storage::StorageFile::GetFileFromPathAsync
(L"MyFile.txt");
}
In dieser nächsten Auflistung wird gezeigt, wie Sie einen Aufruf dieser Methode in eine Aufgabe konvertieren. Beachten Sie, dass wir die to_cx Interop-Hilfsfunktion aufrufen müssen, um das zurückgegebene C++/WinRT-Objekt in ein C++/CX-Handle (auch als Hutobjekt bezeichnet) zu konvertieren.
task<Windows::Storage::StorageFile^> RetrieveStorageFileTask()
{
winrt::Windows::Storage::StorageFile storageFile =
co_await GetStorageFileMemberFunctionAsync();
co_return to_cx<Windows::Storage::StorageFile>(storageFile);
}
Hier ist eine prägnantere Version davon.
task<Windows::Storage::StorageFile^> RetrieveStorageFileTask()
{
co_return to_cx<Windows::Storage::StorageFile>(GetStorageFileMemberFunctionAsync());
}
Und Sie können dieses Muster sogar in eine wiederverwendbare Funktionsvorlage packen und return es genauso zurückgeben, wie Sie normalerweise einen Task zurückgeben.
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());
}
Wenn Ihnen diese Idee gefällt, möchten Sie vielleicht to_task zu interop_helpers.h hinzufügen.
Umschließen Sie eine Aufgabe, die co_return verwendet, mit create_async.
Sie können nicht direkt co_return ein IAsyncXxx^, aber Sie können etwas Ähnliches tun. Wenn Sie eine Aufgabe haben, die kooperativ einen Wert zurückliefert, können Sie sie in einen Aufruf von concurrency::create_async einschließen.
Hier ist ein hypothetisches Beispiel, da es kein Beispiel gibt, das wir von Simple3DGameDX heben können.
Windows::Foundation::IAsyncOperation<bool>^ MyClass::RetrieveBoolAsync()
{
return concurrency::create_async(
[this]() -> concurrency::task<bool> {
bool result = co_await GetBoolMemberFunctionAsync();
co_return result;
});
}
Wie Sie sehen können, können Sie den Rückgabewert aus jeder beliebigen Methode abrufen, die Sie verwenden können co_await.
Portiere concurrency::wait nach co_await winrt::resume_after
Es gibt ein paar Stellen, an denen Simple3DGameDXconcurrency::wait verwendet, um den Thread für kurze Zeit anzuhalten. Hier finden Sie ein Beispiel dafür.
// 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);
}));
...
}
Die C++/WinRT-Version von concurrency::wait ist die Struktur winrt::resume_after. Wir können co_await diese Struktur in einer PPL-Aufgabe verwenden. Hier ist ein Codebeispiel.
// 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);
}));
...
}
Beachten Sie die beiden anderen Änderungen, die wir vornehmen mussten. Wir haben den Typ von GameConstants::InitialLoadingDelay in std::chrono::d uration geändert, und wir haben den Rückgabetyp der Lambda-Funktion explizit vorgenommen, da der Compiler sie nicht mehr ableiten kann.
Migrieren eines <task>void-Rückgabetyps zu winrt::IAsyncXxx
Simple3DGame::LoadLevelAsync
In dieser Phase unserer Arbeit mit Simple3DGameDX verwenden alle Stellen im Projekt, die Simple3DGame::LoadLevelAsync aufrufen, co_await, um die Funktion aufzurufen.
Das bedeutet, dass wir einfach den Rückgabetyp dieser Methode von "task<void>" in "winrt::Windows::Foundation::IAsyncAction" ändern können (der rest davon bleibt unverändert).
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();
}
Es sollte nun recht mechanisch sein, den Rest dieser Methode und ihre Abhängigkeiten (wie etwa m_level und so weiter) nach C++/WinRT zu portieren.
GameRenderer::LoadLevelResourcesAsync
Hier ist die ursprüngliche C++/CX-Version von 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 ist der einzige Ort im Projekt, der GameRenderer::LoadLevelResourcesAsync aufruft und bereits verwendet co_await , um es aufzurufen.
Daher muss GameRenderer::LoadLevelResourcesAsync nicht länger ein Task zurückgeben — stattdessen kann es ein winrt::Windows::Foundation::IAsyncAction zurückgeben. Und die Implementierung selbst ist einfach genug, um vollständig zu C++/WinRT zu portieren. Dazu ist dieselbe Änderung erforderlich, die wir in Portieren von concurrency::wait nach co_await winrt::resume_after vorgenommen haben. Und es gibt keine wesentlichen Abhängigkeiten vom Rest des Projekts, über die man sich Sorgen machen müsste.
Hier erfahren Sie, wie die Methode nach der vollständigen Portierung zu C++/WinRT aussieht.
// 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);
}
Das Ziel – vollständiges Portieren einer Methode zu C++/WinRT
Lassen Sie uns diese exemplarische Vorgehensweise mit einem Beispiel für das Endziel umschließen, indem wir die Methode BasicReaderWriter::ReadDataAsync zu C++/WinRT vollständig portieren.
Beim letzten Mal haben wir diese Methode (im Abschnitt Port ReadDataAsync (hauptsächlich) zu C++/WinRT untersucht und das restliche Projekt unverändert gelassen), es wurde hauptsächlich zu C++/WinRT portiert. Aber es wurde dennoch eine Aufgabe vom Typ Platform::Array<byte>^ zurückgegeben.
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());
}
Anstatt eine Aufgabe zurückzugeben, ändern wir sie, um eine IAsyncOperation zurückzugeben. Anstatt ein Bytearray über diese IAsyncOperation zurückzugeben, geben wir stattdessen ein C++/WinRT IBuffer-Objekt zurück. Das erfordert auch eine geringfügige Änderung am Code an den Aufrufstellen, wie wir noch sehen werden.
So sieht die Methode nach der Portierung ihrer Implementierung, ihres Parameters und des m_location-Datenmembers für die Verwendung von C++/WinRT-Syntax und -Objekten aus.
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() };
}
Wie Sie sehen können, ist BasicReaderWriter::ReadDataAsync selbst viel einfacher, da wir die synchrone Logik, die Bytes aus dem Puffer abruft, in eine eigene Methode eingegliedert haben.
Aber nun müssen wir die Aufrufstellen von dieser Art von Struktur in C++/CX portieren.
task<void> BasicLoader::LoadTextureAsync(...)
{
return m_basicReaderWriter->ReadDataAsync(filename).then(
[=](const Platform::Array<byte>^ textureData)
{
CreateTexture(...);
});
}
Zu diesem Muster in C++/WinRT:
winrt::Windows::Foundation::IAsyncAction BasicLoader::LoadTextureAsync(...)
{
auto textureBuffer = co_await m_basicReaderWriter.ReadDataAsync(filename);
auto textureData = GetBufferView(textureBuffer);
CreateTexture(...);
}
Wichtige APIs
- IAsyncAction-
- IAsyncActionWithProgress<TProgress>
- IAsyncOperation<TResult>
- IAsyncOperationWithProgress<TResult, TProgress>
- implements::get_strong
- concurrency::create_async
- concurrency::create_task
- concurrency::task
- concurrency::task::then
- concurrency::wait
- winrt::fire_and_forget
- winrt::make_self
Zugehörige Themen
Windows developer