# Asynkron filläsning med C++20 coroutines

1 januari 2026 | https://www.ribomation.se/blog/2026/asynkron-fillasning-med-c++20-coroutines/

När du för första gången möter C++20-coroutines är det lätt att känna att du hamnat i en märklig mellanvärld: kraftfulla idéer, men ett API som lämnar ovanligt mycket ansvar till dig. I den här artikeln bygger vi därför ett litet men komplett exempel på asynkron filinläsning – steg för steg – och jämför med ett motsvarande Node.js-program. På vägen reder vi ut vad som faktiskt händer i `co_await`, hur `promise_type` och `awaitable` samverkar, och varför din coroutine plötsligt fortsätter på ”fel” tråd om du inte är uppmärksam.

I två tidigare artiklar har jag beskrivit coroutines i C++20/23. Den första beskriver hur man använder `std::generator<T>` som kom i C++23, och den andra en detaljerad genomgång av C++20 coroutines och det (_udda_) API:t.

Jämför man med coroutines i andra programspråk, såsom JavaScript, så saknas kolossalt många stödfunktioner i C++ vilket gör det svårt att skriva effektiva och lättförståliga asynkrona program. Här är ett litet program som läser in en hel fil med hjälp av en `async function` i Node.js, dvs server-side JavaScript.

```javascript
import {readFile} from 'node:fs/promises'
import process from 'node:process'

async function readfile_async(filename) {
    const data = await readFile(filename, 'utf8');
    return data;
}

let filename = 'shakespeare.txt';
if (process.argv.length > 2) {
    filename = process.argv[2];
}
(async () => {
    try {
        const content = await readfile_async(filename);
        const size = Buffer.byteLength(content, 'utf8');
        console.log('read %d chars from %s', size, filename);
    } catch (e) {
        console.error(`Error reading file ${filename}: ${e.message}`);
    }
})();
```

```shell
JS> node readfile-app.js ./data/shakespeare.txt 
read 5590193 chars from ./data/shakespeare.txt
JS>
```

Tekniskt sett, så returnerar `readFile` ett promise-objekt, som representerar ett framtida resultat. I väntan på detta eller att något blev fel, suspenderas funktionen `readfile_async` tills operationen är klar.

Huvudprogrammet består av en asynkron kontext för att anropa den asynkrona funktionen, vilket vi gör i form av en asynkron IIFE (_Immediately Invoked Function Expression_ — a.k.a., _immediately lambda-expression invocation_). Man brukar normalt inte använda terminologin för coroutines i JavaScript, men det är precis vad det rent tekniskt handlar om, när man deklarerar en funktion (eller lambda) som `async`.

> Strängt taget behöver man inte använda en async IIFE (eller async function), om man exekverar med en hyfsat modern Node.js version, utan syntaxen tillåter s.k. _top-level await_. Dock ville jag ha en dylik här, eftersom vi kommer att göra något liknande i den asynkrona C++ versionen.

Som det ser ut just nu i C++ är det tveksamt — _tycker jag personligen_ — vilken nytta man kan ha av coroutines, såvida man inte är beredd att lägga tid och tankekraft på att implementera det runtime-system som andra programspråk tillhandahåller från första början. Visst kan det vara underhållande och lärorikt att titta på programkod som demonstrerar asynkrona loopar och Fibonacci listor, men inget av detta används i affärskritiska system.

I denna artikel och efterföljande artiklar avser jag att beskriva några olika användningsfall (_use cases_) för hur man kan ha nytta av coroutines, samt hur pass mycket kod man tvingas skriva själv.

Först ut, visar jag hur man kan realisera JavaScript programmet ovan i C++. Först en konventionell synkron version, som blir vår utgångspunkt, och sen en version med coroutines där den största kodmängden utgörs av det runtime-system vi saknar. Här vill jag understryka att denna version är helt hårdkodad för just detta exempel och generaliseringar gör jag i kommande artiklar.

Funktionen `readfile()`
-----------------------

Denna funktion tar ett filnamn, läser innehållet och returnerar det som en sträng. Om filen inte hittas eller om det uppstår fel under läsningen, kastas en exception. Håll i minnet att det är denna funktion vi använder i både det inledande synkrona programmet och den asynkrona versionen som kommer senare i artikeln.

```cpp
//readfile.hxx
#pragma once
#include <filesystem>
#include <string>

namespace ribomation::io {
    namespace fs = std::filesystem;

    auto readfile(fs::path const& filename) -> std::string;
}
```

```cpp
//readfile.cxx
#include <filesystem>
#include <fstream>
#include <string>
#include <stdexcept>
#include "readfile.hxx"

namespace ribomation::io {
    namespace fs = std::filesystem;

    auto readfile(fs::path const& filename) -> std::string {
        auto file = std::ifstream{filename, std::ios::binary};
        if (not file)
            throw std::invalid_argument{"cannot open " + filename.string()};

        file.seekg(0, std::ios::end);
        auto const filesize = file.tellg();
        if (filesize < 0) return {};
        file.seekg(0, std::ios::beg);

        auto buffer = std::string{};
        buffer.resize( static_cast<std::size_t>(filesize) );

        file.read(buffer.data(), static_cast<std::streamsize>(buffer.size()));
        if (not file && not file.eof())
            throw std::runtime_error{"failed reading " + filename.string()};

        return buffer;
    }
}
```

Funktionen öppnar en indatafil med det angivna filnamnet för binär läsning. Om filen inte kan öppnas, kastas ett `std::invalid_argument`\-fel. Filstorleken fås genom att navigera till slutet, läsa av den aktuella positionen och sen navigera tillbaka till början. Om filstorleken är negativ, returneras en tom sträng. Annars läses filen in i en strängbuffert och returneras. Om läsningen misslyckas, kastas ett `std::runtime_error`\-fel.

Synkront program
----------------

I följande korta synkrona program använder vi funktionen `readfile()` för att läsa innehållet från en fil och skriva ut dess storlek.

```cpp
//readfile-app.cxx
    auto filename = fs::path{"./shakespeare.txt"};
    if (argc == 2) filename = argv[1];
    try {
        auto content = ribomation::io::readfile(filename);
        std::println("read {} chars from {}", content.size(), filename.string());
    } catch (std::exception const& err) {
        std::println("failed: {}", err.what());
    }
```

```shell
C++> g++ -std=c++23 -Wall -I ../readfile ../readfile-app.cxx -L ../build -lreadfile -o readfile-sync-app  
C++> ./readfile-sync-app ../data/shakespeare.txt 
read 5590193 chars from ../data/shakespeare.txt
C++> ./readfile-sync-app no-such-file.txt
failed: cannot open no-such-file.txt
C++>
```

Program `readfile-async-app.cxx`
--------------------------------

Jag tänkte visa koden steg-för-steg från programmet bakåt tills vi når runtime-systemet för detta exempel. Givetvis avser jag att förklara de olika stegen och hur de fungerar. Så, här kommer då först applikationskoden och ett körexempel.

```cpp
//readfile-async-app.cxx
#include <filesystem>
#include <print>
#include <exception>
#include "task-coro.hxx"
#include "async-function.hxx"
#include "readfile-async.hxx"

namespace fs = std::filesystem;
namespace io = ribomation::io;

auto LoadFileCoro(fs::path filename) -> io::TaskCoroutine<void> {
    const auto text = co_await io::readfile_async(filename);
    std::println("loaded {} chars from {}", text.size(), filename.string());
    co_return;
}

int main(int argc, char* argv[]) {
    auto filename = fs::path{"./shakespeare.txt"};
    if (argc == 2) filename = argv[1];
    try {
        io::async_function( LoadFileCoro(filename) );
    } catch (std::exception const& error) {
        std::println("failed: {}", error.what());
        return 1;
    }
}
```

Först definierar jag en coroutine som heter `LoadFileCoro`. Den tar ett filnamn som argument och returnerar en `TaskCoroutine<void>` som coroutine-interface. Denna coroutine läser in innehållet i filen asynkront med hjälp av `io::readfile_async` och skriver ut antalet tecken som lästes in. Denna funktion suspenderar coroutine objektet via `co_await`.

I `main` skapar vi denna coroutine och skickar den som argument till funktionen `io::async_function`, vilken fungerar som en exekveringskontext för coroutine objektet. Båda dessa funktioner kommer jag beskriva härnäst och sen arbeta mig bakåt till mer generell kod.

Kompilerar och kör man programmet, kan det se ut så här:

```shell
C++> g++ -std=c++23 -Wall -I ../readfile -I ../lib-async -L../build ../readfile-async-app.cxx \
         -lasync -lreadfile -o readfile-async-app 
C++> ./readfile-async-app ../data/shakespeare.txt 
loaded 5590193 chars from ../data/shakespeare.txt
C++> ./readfile-async-app no-such-file.txt 
failed: cannot open no-such-file.txt
C++>
```

Funktion `async_function`
-------------------------

Varje funktion eller klass — i denna artikel — definierad som en template, har också en specialisering där _värdetypen_ är `void`. Detta eftersom en coroutine promise har antingen en funktion `return_value` eller en `return_void`. Detta gäller också för funktionen `async_function`, som vi börjar med.

```cpp
//async-function.hxx
#pragma once
#include <optional>
#include <exception>
#include <semaphore>
#include "task-coro.hxx"

namespace ribomation::io {
    template<typename ValueType>
    auto async_function(TaskCoroutine<ValueType> task) -> ValueType ...
    
    inline void async_function(TaskCoroutine<void> task) ...
}
```

### Grundversion, som returnerar ett värde

Funktionen `async_function` behövs för att skapa en exekveringskontext för vår coroutine, på samma sätt som JavaScript-exemplets async IIFE. När man anropar en coroutine-funktion får man tillbaka ett coroutine-objekt som är suspenderat från start. Detta objekt kommer inte att köras förrän någon explicit startar det genom att anropa `resume()` på dess handle. Funktionen `async_function` tar hand om hela denna livscykel: den tar emot coroutine-objektet, startar körningen, väntar tills den är klar (eller kastar ett undantag om något går fel), och städar sedan upp genom att förstöra coroutine-objektet. På så sätt kapslar den in all komplexitet kring hanteringen av coroutines och ger oss ett enkelt gränssnitt att arbeta mot, precis som `async`/`await` fungerar i andra språk.

```cpp
template<typename ValueType>
auto async_function(TaskCoroutine<ValueType> task) -> ValueType {
    auto result    = std::optional<ValueType>{};
    auto error     = std::exception_ptr{};
    auto completed = std::binary_semaphore{0};

    auto async_context = [&task, &result, &error, &completed]() -> TaskCoroutine<void> {
        try {
            result = co_await task;
        } catch (...) {
            error = std::current_exception();
        }
        //signal we are done, so the owning function can proceed and return
        completed.release();
        co_return;
    };

    auto coro = async_context();
    //launch the coroutine
    coro.start();
    //wait here, until release() is invoked
    completed.acquire();

    if (error) std::rethrow_exception(error);
    return std::move( result.value() );
}
```

### Specialiserad version, som inte returnerar ett värde

För att hantera coroutines som inte returnerar något värde, har vi en specialiserad version av `async_function` som tar emot en coroutine-funktion som returnerar `void`. Den fungerar på samma sätt som grundversionen, men utan att behöva hantera returnerade värden. Den tar emot coroutine-objektet, startar körningen, väntar tills den är klar (eller kastar ett undantag om något går fel), och städar sedan upp genom att förstöra coroutine-objektet.

```cpp
inline void async_function(TaskCoroutine<void> task) {
    auto error = std::exception_ptr{};
    auto completed = std::binary_semaphore{0};

    auto async_context = [&task, &error, &completed]() -> TaskCoroutine<void> {
        try {
            co_await task;
        } catch (...) {
            error = std::current_exception();
        }
        completed.release();
        co_return;
    };

    auto coro = async_context();
    coro.start();
    completed.acquire();

    if (error) std::rethrow_exception(error);
}
```

Funktion `readfile_async`
-------------------------

Delsystemet _readfile_ består av funktionen `readfile_async`, som skapar och returnerar ett objekt av klassen `ReadfileAwaitable`, vilken i sin tur är ansvarig för att anropa vår ursprungliga `readfile` funktion på en separat worker thread.

```cpp
//readfile-async.hxx
#pragma once
#include <optional>
#include <string>
#include <filesystem>
#include <memory>
#include <coroutine>

namespace ribomation::io {
    namespace fs=std::filesystem;
    
    class ReadfileAwaitable {...};
    
    inline auto readfile_async(fs::path filename) -> ReadfileAwaitable...
```

Så här ser själva funktionen ut. Den tar emot ett filnamn, skapar och returnerar ett objekt av klassen `ReadfileAwaitable`, vilken tar emot filnamnet ifråga.

```cpp
inline auto readfile_async(fs::path filename) -> ReadfileAwaitable {
    return ReadfileAwaitable{ std::move(filename) };
}
```

### Klass `ReadfileAwaitable`

Denna är ansvarig för att anropa den synkrona versionen av `readfile`, vilket görs på en separat _fire&forget_ worker thread.

#### Awaitable vs. Awaiter

Innan vi tittar på detaljerna i klassen är det viktigt att skilja på två centrala begrepp i C++ coroutines: _awaitable_ och _awaiter_.

En **awaitable** är ett objekt som kan användas tillsammans med operatorn `co_await`. För att vara "awaitable" måste objektet antingen ha en medlemsfunktion `operator co_await` eller så måste det finnas en fri funktion med samma namn som returnerar en awaiter.

En **awaiter** är det objekt som faktiskt styr suspensionen. Det måste implementera tre specifika metoder: `await_ready()`, `await_suspend()` och `await_resume()`. I vårt fall implementerar klassen `ReadfileAwaitable` dessa tre metoder direkt. Det innebär att klassen fungerar som både en awaitable (den kan "awaitas") och en awaiter (den sköter logiken).

#### Varför namnet ReadfileAwaitable?

Valet av namn handlar om semantik och läsbarhet för användaren av API:et. När vi anropar `readfile_async(filename)` får vi tillbaka något som vi förväntar oss att kunna vänta på med `co_await`. Från anroparens perspektiv är objektet en "väntbar" entitet (awaitable). Att den under huven även råkar implementera awaiter-gränssnittet är en teknisk detalj för att förenkla implementationen och slippa skapa två separata klasser. Namnet betonar alltså objektets roll i det asynkrona flödet snarare än dess tekniska implementation.

#### Klass-struktur

```cpp
class ReadfileAwaitable {
    struct CompletionState {
        std::optional<std::string>  content{};
        std::exception_ptr          error{};
    };
    fs::path  filename{};
    std::shared_ptr<CompletionState>  state = std::make_shared<CompletionState>();

public:
    explicit ReadfileAwaitable(fs::path filename_) : filename(std::move(filename_)) {}
    bool await_ready() const noexcept { return false; }
    void await_suspend(std::coroutine_handle<> invoking_task);
    auto await_resume() -> std::string;
};
```

Den inre klassen `CompletionState` representerar utfallet, när målfunktionen är klar; antingen gick det bra och `content` har ett innehåll eller så blev något fel och `error` innehåller information om detta. Detta har viss likhet med resultatet av ett _Promise_ objekt i JavaScript. Metoden `await_ready()` returnerar `false`, så att systemet alltid suspenderar anroparen, vilket säkerställer att worker-thread kickar igång. Notera också att anroparen fortsätter sedan exekvera på worker-thread, d.v.s., anropet `invoking_task.resume()` sker i `worker_body` lambdan.

#### Implementation av medlemsfunktioner

```cpp
//readfile-async.cxx
#include <thread>
#include "readfile.hxx"
#include "readfile-async.hxx"

namespace ribomation::io {
    void ReadfileAwaitable::await_suspend(std::coroutine_handle<> invoking_task) {
        auto state_ptr    = state;
        auto filename_cpy = std::move(filename);

        auto worker_body = [state_ptr, invoking_task, filename = std::move(filename_cpy)]() mutable {
            try {
                //invoke the sync func on the worker thread
                state_ptr->content = readfile(filename); 
            } catch (...) {
                //save error, if any
                state_ptr->error = std::current_exception(); 
            }
            //resume the invoking task, and run on this worker thread
            invoking_task.resume(); 
        };
        auto worker = std::thread{worker_body};
        //run it async, don't bother to join it
        worker.detach(); 
    }

    auto ReadfileAwaitable::await_resume() -> std::string {
        if (state->error) std::rethrow_exception(state->error);
        return std::move(state->content.value());
    }
}
```

Funktionen `await_suspend` skapar en worker thread och i denna anropar vår ursprungliga synkrona `readfile` funktion. I `worker_body` lambdan syns det tydligt att beroende på utfall så kommer `content` eller `error` populeras. Anropet `worker.detach()` innebär att tråden lämnas "_fire & forget_", så att funktionen `await_suspend` kan returnera.

I funktionen `await_resume` överförs resultatet, som finns lagrat i variabeln `state`. Gick det bra så flyttas innehållet i `content` vidare eller så kastas en exception med innehållet i `error`.

TaskCoroutine
-------------

Sista delen följer nu och utgör kärnan i vårt runtime-system, vilket är helt frikopplat från readfile funktionaliteten. Jag börjar med en översikt av innehållet i header-filen.

```cpp
//task-coro.hxx
#pragma once
#include <optional>
#include <coroutine>
#include <exception>
#include <utility>

namespace ribomation::io {
    //forward decl
    template<typename ValueType> struct TaskCoroutine;

    namespace impl {
        template<typename ValueType>
        struct TaskPromise {...};

        template<>
        struct TaskPromise<void> {...};
    }
    
    template<typename ValueType>
    struct TaskCoroutine {...};
    
    template<>
    struct TaskCoroutine<void> {...};
}

namespace ribomation::io::impl {
    template<typename ValueType>
    auto TaskPromise<ValueType>::get_return_object() -> TaskCoroutine<ValueType> {...}
    
    inline auto TaskPromise<void>::get_return_object() -> TaskCoroutine<void> {...}
}
```

Eftersom vi har ett cirkulärt beroende mellan interface-klassen och promise-klassen, så tvingas man till denna (klumpiga) struktur, där vi börjar med en forward-deklaration av coroutine interface-klassen, följt av delar av promise-klassen, och sedan själva interface klassen, för att till slut implementera resterande delar av promise-klassen — inte snyggt om du frågar mig.

### Klass `TaskCoroutine<T>`

`TaskCoroutine<T>` fungerar som det externa gränssnittet (interface-klassen) för en asynkron uppgift. Dess främsta syfte är att agera som en brygga mellan anroparen och coroutinens interna tillstånd. Den är designad som en "RAII-wrapper" kring en `std::coroutine_handle`, vilket innebär att den tar ansvar för att städa upp (förstöra) coroutinen när objektet går ur scope.

**Huvudsakliga egenskaper:**

*   **Ägarskap:** Den är inte kopierbar (deleted copy constructor/assignment), men flyttbar (moveable). Detta säkerställer att endast en instans i taget äger coroutinen.
*   **Livscykel:** I destruktorn förstörs coroutinen via `handle.destroy()`, vilket frigör minnet för promise-objektet och dess lokala variabler.
*   **Manuell start:** Genom metoden `start()` kan anroparen explicit kicka igång exekveringen. Eftersom `initial_suspend` i promise-klassen returnerar `std::suspend_always`, startar coroutinen i ett suspenderat tillstånd och väntar på denna signal.

#### Inre klass `LaunchAwaiter`

Detta är den mest centrala delen för att möjliggöra sammansättning av coroutines (dvs. att kunna skriva `co_await some_task`). `LaunchAwaiter` implementerar det protokoll som C++ kräver för att ett objekt ska vara "awaitable".

När du skriver `co_await task` i en annan coroutine, händer följande via `LaunchAwaiter`:

1.  **`await_ready()`**: Kontrollerar om uppgiften redan är klar. Om den är det behöver vi inte suspendera den anropande coroutinen.
2.  **`await_suspend(std::coroutine_handle<> other_task)`**: Detta är den "magiska" länken. Här sparas den anropande coroutinen (`other_task`) som en s.k. _continuation_ inuti den aktuella uppgiftens promise-objekt. Därefter anropas `this_task.resume()`, vilket startar exekveringen av den uppgift vi väntar på.
3.  **`await_resume()`**: Denna metod anropas när den väntade uppgiften är klar och kontrollen återgår till den anropande coroutinen. Den har två viktiga uppgifter:
    *   **Felhantering:** Om ett undantag kastades inuti coroutinen (och fångades upp av `unhandled_exception` i promise-objektet), kommer `await_resume` att kasta om det här via `std::rethrow_exception`.
    *   **Resultatleverans:** Den hämtar det lagrade värdet från promise-objektet och returnerar det (via `std::move`) till anroparen.

#### Sammanfattning av flödet

`TaskCoroutine` gör det möjligt att bygga kedjor av asynkrona anrop. Genom `operator co_await()` returnerar den en `LaunchAwaiter` som ser till att:

1.  Den anropande coroutinen pausas.
2.  Den nya coroutinen startas.
3.  När den nya är klar, återupptas den första med resultatet (eller ett fel) till hands.

Detta efterliknar hur `Promise` och `await` fungerar i JavaScript, men i en strikt typad C++-miljö.

```cpp
template<typename ValueType>
struct TaskCoroutine {
    using promise_type = impl::TaskPromise<ValueType>;
    using handle_type  = std::coroutine_handle<promise_type>;
private:
    handle_type handle{};
public:
    explicit TaskCoroutine(handle_type h) : handle(h) {}
    ~TaskCoroutine() { if (handle) handle.destroy(); }

    TaskCoroutine(TaskCoroutine const&) = delete;
    TaskCoroutine(TaskCoroutine && rhs) noexcept :handle(std::exchange(rhs.handle, {})) {}

    auto operator =(TaskCoroutine const&) -> TaskCoroutine& = delete;
    auto operator =(TaskCoroutine && rhs) noexcept -> TaskCoroutine& {
        if (this != &rhs) {
            if (handle) handle.destroy();
            handle = std::exchange(rhs.handle, {});
        }
        return *this;
    }

    void start() {
        if (handle && not handle.done()) handle.resume();
    }

    class LaunchAwaiter {
        handle_type this_task;
    public:
        explicit LaunchAwaiter(handle_type h) : this_task(h) {}

        bool await_ready() noexcept { return not this_task || this_task.done(); }
        void await_suspend(std::coroutine_handle<> other_task) noexcept {
            this_task.promise().continuation = other_task;
            this_task.resume();
        }
        auto await_resume() -> ValueType {
            if (auto& p = this_task.promise(); p.error)
                std::rethrow_exception(p.error);
            return std::move( this_task.promise().value.value() );
        }
    };

    auto operator co_await() const noexcept -> LaunchAwaiter { 
        return LaunchAwaiter{handle}; 
    }
};
```

### Klass `TaskCoroutine<void>`

Detta är en specialisering för fallet då inget värde returneras från en färdigkörd coroutine.

```cpp
template<>
struct TaskCoroutine<void> {
    using promise_type = impl::TaskPromise<void>;
    using handle_type  = std::coroutine_handle<promise_type>;
private:
    handle_type handle{};
public:
    explicit TaskCoroutine(handle_type h) : handle(h) {}
    ~TaskCoroutine() { if (handle) handle.destroy(); }

    TaskCoroutine(TaskCoroutine const&) = delete;
    TaskCoroutine(TaskCoroutine && rhs) noexcept :handle(std::exchange(rhs.handle, {})) {}

    auto operator =(TaskCoroutine const&) -> TaskCoroutine& = delete;
    auto operator =(TaskCoroutine && rhs) noexcept -> TaskCoroutine& {
        if (this != &rhs) {
            if (handle) handle.destroy();
            handle = std::exchange(rhs.handle, {});
        }
        return *this;
    }

    void start() {
        if (handle && not handle.done()) handle.resume();
    }

    class LaunchAwaiter {
        handle_type this_task;
    public:
        explicit LaunchAwaiter(handle_type h) : this_task(h) {}
        bool await_ready() noexcept { return not this_task || this_task.done(); }
        void await_suspend(std::coroutine_handle<> other_task) noexcept {
            this_task.promise().continuation = other_task;
            this_task.resume();
        }
        void await_resume() {
            if (auto& p = this_task.promise(); p.error)
                std::rethrow_exception(p.error);
        }
    };

    auto operator co_await() const noexcept -> LaunchAwaiter { 
        return LaunchAwaiter{handle}; 
    }
};
```

### Klass `TaskPromise<T>`

Om `TaskCoroutine` är det yttre handtaget, så är `TaskPromise` coroutinens interna tillståndslager och kontrollcenter. I C++ coroutine-ramverket är det promise-objektet som definierar hur coroutinen beter sig vid start, avslut och när den returnerar värden.

**Huvudsakliga ansvarsområden:**

*   **Resultatlagring:** Den använder en `std::optional<ValueType>` för att lagra det slutgiltiga värdet som produceras av coroutinen.
*   **Felhantering:** Genom `unhandled_exception()` fångar den upp alla undantag som kastas inuti coroutinen. Istället för att programmet kraschar, lagras felet i en `std::exception_ptr` så att det kan kastas om senare när resultatet efterfrågas via `co_await`.
*   **Livscykelkontroll:**
    *   `initial_suspend()`: Returnerar `std::suspend_always`, vilket gör att coroutinen skapas i ett pausat läge. Den börjar inte köra förrän någon explicit anropar `start()` eller `co_await`.
    *   `get_return_object()`: Skapar och returnerar det `TaskCoroutine`\-objekt som anroparen får i handen.

#### Inre klass `CompletionAwaiter`

`CompletionAwaiter` används i `final_suspend()` och är den mekanism som sköter kedjekopplingen (continuation) när en coroutine har kört klart. Dess roll är kritisk för att "nästlade" coroutines ska fungera.

**Så fungerar den:**

1.  **`await_ready()`**: Returnerar `false`, vilket tvingar coroutinen att suspendera en sista gång innan den förstörs. Detta är viktigt för att promise-objektet (och dess data) ska finnas kvar tills anroparen har hunnit hämta resultatet.
2.  **`await_suspend(std::coroutine_handle<TaskPromise> h)`**: Detta är hjärtat i asynkron kedjekoppling. När coroutinen når sitt slut, kontrollerar den om det finns en sparad `continuation` (en anropande coroutine som väntar på svar). Om det finns en sådan, anropas `cont.resume()`.
3.  **Återgång:** Detta gör att kontrollen hoppar tillbaka till den coroutine som gjorde `co_await`, precis efter att den asynkrona operationen slutförts.

#### Sammanfattning av flödet

När en coroutine exekveras, interagerar den med `TaskPromise` på följande sätt:

1.  **Start:** Coroutinen pausas direkt (`initial_suspend`).
2.  **Exekvering:** När den körs och når en `co_return value;`, anropas `return_value()`, som sparar resultatet i promise-objektet.
3.  **Avslut:** Vid slutet anropas `final_suspend()`, där `CompletionAwaiter` ser till att väcka den som väntade på resultatet, så att flödet kan fortsätta asynkront.

Utan `TaskPromise` och dess förmåga att spara undantag och hantera continuations, skulle vi inte kunna bygga de sekventiella asynkrona flöden som vi är vana vid från andra moderna språk.

```cpp
template<typename ValueType>
struct TaskPromise {
    std::optional<ValueType>  value{};
    std::exception_ptr        error{};
    std::coroutine_handle<>   continuation{};

    struct CompletionAwaiter {
        bool await_ready() noexcept { return false; }
        void await_resume() noexcept {}
        void await_suspend(std::coroutine_handle<TaskPromise> h) noexcept {
            if (auto cont = h.promise().continuation; cont) cont.resume();
        }
    };

    auto get_return_object() -> TaskCoroutine<ValueType>;
    auto initial_suspend() noexcept { return std::suspend_always{}; }
    auto final_suspend() noexcept { return CompletionAwaiter{}; }

    template<typename ReturnType>
    void return_value(ReturnType&& v) {
        value.emplace( std::forward<ReturnType>(v) );
    }
    void unhandled_exception() noexcept { error = std::current_exception(); }
};

//...

template<typename ValueType>
auto TaskPromise<ValueType>::get_return_object() -> TaskCoroutine<ValueType> {
    auto h = std::coroutine_handle<TaskPromise>::from_promise(*this);
    return TaskCoroutine<ValueType>{h};
}
```

### Klass `TaskPromise<void>`

Detta är specialiseringen av promise-klassen, för det fall att task coroutine inte returnerar något.

```cpp
template<>
struct TaskPromise<void> {
    std::exception_ptr        error{};
    std::coroutine_handle<>   continuation{};

    struct CompletionAwaiter {
        bool await_ready() noexcept { return false; }
        void await_resume() noexcept {}
        void await_suspend(std::coroutine_handle<TaskPromise> h) noexcept {
            if (auto cont = h.promise().continuation; cont) cont.resume();
        }
    };

    auto get_return_object() -> TaskCoroutine<void>;
    auto initial_suspend() noexcept { return std::suspend_always{}; }
    auto final_suspend() noexcept { return CompletionAwaiter{}; }

    void return_void() noexcept {}
    void unhandled_exception() noexcept { error = std::current_exception(); }
};

//...

inline auto TaskPromise<void>::get_return_object() -> TaskCoroutine<void> {
    auto h = std::coroutine_handle<TaskPromise>::from_promise(*this);
    return TaskCoroutine<void>{h};
}
```

Sammanfattning
--------------

Oj, oj som utlovat inledningsvis; en massa ramverkskod bara för att anropa en konventionell/synkron funktion asynkront från en coroutine. Som jag också påpekat tidigare, koden du sett här ovan är också hårdkodad för att just anropa vår readfile funktion, samt att exekveringskontrollen fortsätter på en annan thread, i stället för att återvända till den thread som anropade. Vill man ha den funktionaliteten så behövs en scheduler, vilket jag ämnar visa hur i en annan artikel.

Att implementera ett eget runtime-system för coroutines i C++ kan vid en första anblick verka övermäktigt, men som vi har sett i analysen av `TaskCoroutine<T>` och `TaskPromise<T>` handlar det i grunden om att etablera ett tydligt protokoll för hur data och kontrollflöde ska vandra mellan olika asynkrona punkter.

Genom att använda **TaskPromise** som coroutinens interna tillståndslager får vi en robust hantering av både resultat och eventuella undantag. Med den smarta kopplingen i **LaunchAwaiter** och **CompletionAwaiter** kan vi skapa kedjor av asynkrona anrop som känns lika naturliga som i JavaScript, trots att C++ kräver att vi själva bygger de kugghjul som får maskineriet att snurra.

Även om C++ coroutines innebär en hel del "boilerplate"-kod, ger det oss en enorm flexibilitet och prestanda. Vi har nu ett ramverk där vi kan skriva kod som ser synkron och lättläst ut, men som under huven utnyttjar trådar och asynkrona resurser på ett effektivt sätt. I kommande artiklar ska vi titta närmare på hur vi kan generalisera detta ytterligare för att bygga ännu mer kraftfulla asynkrona system.
