7. Async And Coroutines¶
submit_raw()/submit<T>() return a zlink::framework::task_t. There are three ways to consume
the result.
Turning On Coroutine Execution¶
By default, the client keeps the same blocking submit semantics as existing code. To free up the calling thread while waiting on HTTP, specify coroutine execution in the client configuration.
auto client = zlink::http_client::client_t::create ("http://127.0.0.1:18080")
.coroutines ()
.build ();
.coroutines() uses the HTTP client's internal scheduler. This scheduler does not reveal
Boost.Asio, Boost.Beast, or OpenSSL runtime types in the public header.
For a case like a server runtime, where you need to directly decide where the coroutine resumes, inject a framework queue adapter as the resume scheduler.
auto server_resume_scheduler =
std::make_shared<zlink::http_client::framework_resume_scheduler_t> (
[&server_queue] (std::function<void ()> continuation) {
server_queue.post (std::move (continuation));
});
auto client = zlink::http_client::client_t::create ("https://matchmaking.internal")
.coroutines (server_resume_scheduler)
.build ();
If needed, you can separate the scheduler that executes the HTTP work from the scheduler that resumes the coroutine, and provide both.
auto client = zlink::http_client::client_t::create ("https://matchmaking.internal")
.coroutines (http_execute_scheduler, server_resume_scheduler)
.build ();
Here, the execute scheduler runs the HTTP exchange work, and the resume scheduler decides where the continuation and callback run after completion.
co_await (The Standard Inside A Framework Handler)¶
Framework runtime/handler code co_awaits a client configured with .coroutines(...). On success
you get the value; on failure, framework_exception_t is thrown.
zlink::framework::task_t<void>
notify_match_result (zlink::http_client::client_t &client, const match_result_t &result)
{
auto response = co_await client.post ("/matches/" + result.match_id + "/result")
.body (result)
.submit<ack_res_t> ();
// response: http_response_t<ack_res_t>
if (response.body.accepted) {
co_return;
}
throw zlink::framework::framework_exception_t (
zlink::framework::framework_error_kind_t::request_failed,
"match result was not accepted");
}
Blocking: .result() / fetch()¶
task.result() stops the calling thread until the result arrives, returning a result_t.
fetch<T>() additionally unwraps the wrapper and turns a failure into an exception.
auto result = client.get ("/leaderboard").submit<leaderboard_t> ().result ();
auto board = client.get ("/leaderboard").fetch<leaderboard_t> (); // equivalent + unwrapped
Callback Submit¶
submit<T>(callback) delivers the result_t to the callback on completion. On a client with
coroutine execution turned on, after the HTTP completes, typed decoding and the callback continue at
the location the resume scheduler decides.
client.post ("/games")
.body (create_game_http_req_t{.game_name = "ranked-match-0611"})
.submit<create_game_http_res_t> ([] (const auto &result) {
if (!result) {
log_error (result.error ()->what ());
return;
}
on_game_created (result.value ().body);
});
What To Use Where — The Blocking Rule¶
Don't use a blocking access (
.result(),fetch<T>()) on a framework runtime/handler thread. Stopping the runtime thread also blocks other work meant to be processed on that same thread.
| Call Site | Recommended |
|---|---|
| framework handler / actor / spot code | co_await submit<T>() |
| test code | fetch<T>() or .result() |
| client scenario/CLI/batch | fetch<T>() or .result() |
| glue code where a callback style is natural | submit<T>(callback) |
Streaming Callback Location¶
body_stream(provider)'s provider and download(sink)'s sink are called on the execute scheduler
worker running the HTTP work. Don't touch server handler state directly inside this callback — use
a thread-safe queue or a server scheduler post if needed.