渡劫 C++ 协程(9):一个简单的示例

截止目前,我们一直专注于构建基于协程 API 的框架支持,这次我们用这些框架来写个简单的示例,并以此来结束整个系列的内容。

准备工作

在本文当中,我将使用前文实现好的 Task 来发起一个简单的网络请求。

我会借助一些已有的框架来完成这次的目标:

1
2
3
cpp-httplib/0.10.4
openssl/3.0.2
nlohmann_json/3.10.5

这些框架可以通过 conan 很轻松的完成安装。

示例实现

首先我们给出发起网络请求的核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 用协程包装网络请求,请求的处理调度到 std::async 上
Task<std::string, AsyncExecutor> http_get(std::string host, std::string path) {
httplib::Client cli(host);

// 阻塞地发起网络请求
auto res = cli.Get(path.c_str());

if (res) {
// 返回响应内容,类型为 std::string
co_return res->body;
} else {
co_return httplib::to_string(res.error());
}
}

使用 httplib 来完成网络请求的处理非常简单直接,我们只需要把 url 传入即可。通常我们的网络请求都会在 io 线程当中发起,因此我们将其调度到 AsyncExecutor 上。

接下来,我们再定义一个协程来调用 http_get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Task<void, LooperExecutor> test_http() {
try {
debug("send request..."); // Looper 线程上执行

// 发起网络请求,切换线程,当前协程挂起,Looper 线程被释放(此时 Looper 线程可以去调度其他任务)
auto result = co_await http_get("https://api.github.com", "/users/bennyhuo");
// 请求返回,当前协程接着在 Looper 线程上调度执行
debug("done.");

// 业务逻辑处理,解析 json
auto json = nlohmann::json::parse(result);

// 打印 json 内容
debug(json.dump(2));
// 假装这是其他业务处理
debug(json["login"], json["url"]);
} catch (std::exception &e) {
debug(e.what());
}
}

程序运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
22:10:54.046 [Thread-08056] (main.cpp:27) test_http: send request...
22:10:54.953 [Thread-08056] (main.cpp:29) test_http: done.
22:10:54.953 [Thread-08056] (main.cpp:31) test_http: {
"avatar_url": "https://avatars.githubusercontent.com/u/6336960?v=4",
"bio": "Google Developer Expert @Kotlin",
"blog": "https://www.bennyhuo.com",
... 中间内容很多,省略掉 ...
"updated_at": "2022-03-23T13:51:26Z",
"url": "https://api.github.com/users/bennyhuo"
}
22:10:54.953 [Thread-08056] (main.cpp:32) test_http: "bennyhuo"
22:10:54.954 [Thread-08056] (main.cpp:33) test_http: "https://api.github.com/users/bennyhuo"
22:10:54.954 [Thread-08056] (main.cpp:34) test_http: "Google Developer Expert @Kotlin"

在这个示例当中,我们没有使用协程来解决阻塞的问题,而是将一个异步的请求封装成同步的代码。test_http 当中的代码全程在 Looper 线程当中执行,尽管中间穿插了一个异步网络请求,但这看上去丝毫没有影响程序的连贯性和简洁性。

小结

本文的内容相对轻松,因为我们终于停止了基于协程的基础 API 的探索。

实际上,如果你发现你用到的某些 API 提供了异步回调,你完全可以使用 Awaiter 对其提供 co_await 的支持。


关于作者

霍丙乾 bennyhuo,Google 开发者专家(Kotlin 方向);《深入理解 Kotlin 协程》 作者(机械工业出版社,2020.6);《深入实践 Kotlin 元编程》 作者(机械工业出版社,2023.8);前腾讯高级工程师,现就职于猿辅导