9. 一个简单的示例

Benny Huo大约 3 分钟

9. 一个简单的示例

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

准备工作

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

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

cpp-httplib/0.10.4
openssl/3.0.2
nlohmann_json/3.10.5

这些框架可以通过 conanopen in new window 很轻松的完成安装。

示例实现

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

// 用协程包装网络请求,请求的处理调度到 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

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());
  }
}

程序运行结果如下:

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);前腾讯高级工程师,现就职于猿辅导

上次编辑于:
贡献者: bennyhuo