C++ von den Grundlagen bis zum Profi — Folge 21
Suspendierung, Fortsetzung, Lebensdauer und Abbruch von Coroutines verstehen
Die Artikel in diesem Blog wurden zu Lehrzwecken verfasst. Sie können Fehler enthalten; prüfen Sie die Aussagen daher mit dem Ziel-Compiler und der verwendeten Standardbibliothek.
Eine Coroutine kann eine Funktion unterbrechen und später am gleichen Zustand fortsetzen. Sie erzeugt nicht automatisch Threads oder asynchrones I/O, sondern ist ein Sprachprotokoll zum Aufbau asynchroner Kontrollflüsse.
Zustand liegt im Coroutine Frame
Bei co_await, co_yield oder co_return transformiert der Compiler die Funktion in eine Zustandsmaschine, die lokale Variablen und Fortsetzungsposition speichert.
Task<Texture> LoadTextureAsync(Path path)
{
FileBytes bytes = co_await ReadFileAsync(path);
DecodedImage image = co_await DecodeAsync(std::move(bytes));
co_return UploadTexture(std::move(image));
}
Task und Awaitables werden vom Projekt oder einer Async-Bibliothek definiert. Standard-C++20 legt die Coroutine-Transformation fest, bietet aber keinen universellen Executor oder Netzwerk-Event-Loop.
co_await fragt das Awaitable nach dem Anhalten
await_ready Ist das Ergebnis ohne Suspendierung verfügbar?
await_suspend Wer besitzt den Handle und setzt fort?
await_resume Welcher Wert oder Fehler entsteht danach?
Ist die Arbeit schon fertig, kann die Ausführung direkt weitergehen. Andernfalls muss der Coroutine Handle sicher an Scheduler oder I/O übergeben und genau einmal fortgesetzt werden.
Frame-Besitz ist das zentrale Lebensdauerproblem
Was passiert, wenn der Aufrufer den Task sofort verwirft: cancel, detach oder anderer Besitzer? Ein Callback, der einen zerstörten Frame fortsetzt, verursacht use-after-free.
Auch Referenzen können während der Suspendierung ablaufen.
Task<void> ShowLater(const Player& player)
{
co_await NextFrame();
Draw(player.Name); // Lebt player noch?
}
Kopieren Sie benötigte Daten in den Frame, verwenden Sie stabile Handles oder machen Sie die Lebensdauergarantie des Aufrufers ausdrücklich.
Abbruch und Fehler sind ebenfalls Protokolle. Cancellation muss wartende Operationen wecken, Aufräumen zulassen und ein Ergebnis zum übergeordneten Task übertragen. Der Promise-Typ entscheidet, ob Exceptions beim co_await erneut ausgelöst oder als Fehlerwert dargestellt werden.
Coroutines lesen sich sequenziell, laufen aber über mehrere Zeitpunkte. Dokumentieren und testen Sie Frame-Besitz, Executor, Fortsetzungsthread, Cancellation und Fehlerweitergabe der tatsächlich verwendeten Task-Implementierung.