C++ de los fundamentos al dominio — Lección 21
Comprender suspensión, reanudación, vida útil y cancelación de coroutines
Los artículos de este blog se han escrito con fines didácticos. Pueden contener errores; contrasta la información con el compilador y la biblioteca estándar de tu plataforma.
Una coroutine puede suspender una función y reanudarla después desde el mismo estado. No crea hilos ni I/O asíncrono automáticamente; es un protocolo del lenguaje para construir control asíncrono.
El estado vive en el Coroutine frame
Al usar co_await, co_yield o co_return, el compilador transforma la función en una máquina de estados que conserva variables locales y el punto de reanudación.
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 y los awaitables del ejemplo son contratos definidos por el proyecto o una biblioteca. C++20 estándar define la transformación de coroutines, pero no incluye un executor universal ni un event loop de red.
co_await pregunta cómo suspender
await_ready ¿Está listo el resultado sin suspensión?
await_suspend ¿Quién posee el handle y lo reanuda?
await_resume ¿Qué valor o error se produce después?
Si el trabajo terminó, la ejecución puede continuar. En caso contrario, el Coroutine handle debe entregarse de forma segura al scheduler o I/O y reanudarse exactamente una vez.
La propiedad del frame decide la vida útil
Si el llamador descarta el Task, ¿la operación se cancela, se separa o queda en otro propietario? Un callback que reanuda un frame destruido causa use-after-free.
Las referencias también pueden expirar durante la suspensión.
Task<void> ShowLater(const Player& player)
{
co_await NextFrame();
Draw(player.Name); // ¿Sigue vivo player?
}
Copia los datos necesarios al frame, usa handles estables o expresa la garantía de vida del llamador.
La cancelación y los errores también son protocolos. Cancelar debe despertar operaciones, permitir limpieza y propagar un resultado. El promise type decide si una excepción se guarda y relanza en co_await o se convierte en un valor de error.
Las coroutines se leen como código secuencial, pero actúan en varios momentos. Documenta propiedad del frame, executor, hilo de reanudación, cancelación y propagación, y prueba la implementación de Task que realmente uses.