
如何用 std::stop_token 给 HTTP 请求加取消能力
异步 HTTP 请求本身不自带取消语义,C++20 的 std::stop_token 是目前最轻量、标准且线程安全的取消机制。关键不是“怎么发请求”,而是“在哪埋入检查点”。你得在阻塞点(比如 recv、poll、或自定义等待循环)主动轮询 stop_token 状态,而不是等底层库“自动响应”。
常见错误是只在发起前检查一次 stop_token,结果请求已发出却无法中断——网络 I/O 一旦进入系统调用,没信号或超时就卡死。真正有效的做法是在每次可能长时间等待前插入 if (stoken.stop_requested()) return;。
- Linux 下用
poll()或epoll_wait()时,把stop_token对应的文件描述符(如 eventfd)加入监听集,实现“取消即就绪” - Windows 下可用
WaitForMultipleObjects同时等 socket 事件和CreateEvent句柄 - 若用第三方库(如 libcurl),它不原生支持
std::stop_token,必须用CURLOPT_XFERINFOFUNCTION+ 自定义回调,在回调里查stop_token并返回非零值触发中止
libcurl 异步模式下怎么接住 std::stop_source
libcurl 没有 std::stop_token 接口,但它的多接口(curl_multi_*)允许你在主循环里插手控制流。核心思路是:用 std::stop_source 触发后,不再调用 curl_multi_perform(),并尽快调用 curl_multi_remove_handle() 清理资源。
容易踩的坑是直接调 curl_easy_cleanup() —— 如果 handle 还挂在 multi 上,会 crash。必须先移除再清理。
立即学习“C++免费学习笔记(深入)”;
- 每个请求绑定一个独立的
std::stop_source,避免多个请求共用一个 token 导致误杀 - 在
curl_multi_perform()返回后立刻检查stop_token.stop_requested(),若为真,立即调curl_multi_remove_handle(multi, easy),然后 break 主循环 - 不要依赖
CURLOPT_TIMEOUT_MS替代取消:它只管总耗时,不响应中途取消指令
为什么不能直接 kill 线程来取消 HTTP 请求
强行终止线程(比如 std::thread::detach() 后不管,或用平台级 kill)会导致资源泄漏和未定义行为:socket fd 不释放、SSL 上下文未清理、libcurl 内部缓冲区残留、甚至 malloc 堆损坏。C++ 标准明确禁止从外部销毁正在执行的线程。
真实场景中,你会看到 close(): Invalid argument 或 double free or corruption 错误,尤其在启用 HTTPS 和重定向时更频繁。
- 所有网络 I/O 必须走协作式取消:线程自己检查退出条件并有序关闭
- SSL/TLS 握手阶段最危险——OpenSSL 的
SSL_connect()是阻塞的,必须用非阻塞 socket +SSL_get_error()判断SSL_ERROR_WANT_READ/WANT_WRITE,才能在中间插入stop_token检查 - 即使用了
std::jthread,它的析构也只是调request_stop(),不会强制停,这点必须清楚
异步取消后怎么确保状态干净(尤其是重试与连接池)
取消不是终点,而是状态转换的起点。HTTP 客户端模块如果带连接复用(keep-alive)或请求重试逻辑,取消后必须显式标记连接“不可复用”,否则下次请求可能拿到一个半截断开的 socket。
典型表现是后续请求收到 Connection: close 却仍往旧连接写,导致 EPIPE 或静默失败。
- 每次取消后,将对应连接句柄标记为
dirty,从连接池中移出或置为 invalid - 重试逻辑里要过滤掉已被取消的请求:检查
stop_token.stop_requested()再决定是否进重试队列 - 避免在析构函数里做网络 cleanup:对象可能被移动、异常栈展开时调用顺序不确定,应改用 RAII wrapper 显式
cancel()+join()或wait()
最难处理的是 DNS 解析阶段——它通常由系统库完成,不暴露取消句柄。这时候只能靠设置 CURLOPT_TIMEOUT_MS 限界,或者换用支持 cancel 的 DNS 库(如 c-ares),别指望 std::stop_token 能穿透到 getaddrinfo 里面。










