std::stop_callback 是响应已触发取消请求、执行清理逻辑的线程安全钩子,绑定到 std::stop_source 后按构造逆序自动调用回调,仅执行一次,用于资源释放而非发起取消。

std::stop_callback 是什么,它在取消流程里起什么作用
std::stop_callback 不是“主动取消”的工具,而是响应已触发的取消请求、执行清理逻辑的钩子。它绑定到 std::stop_source,一旦该 source 调用 request_stop()(或被其他线程/协程触发),所有已注册的 std::stop_callback 实例会**按构造顺序的逆序**自动调用其回调函数——这个时机是线程安全的,且保证只执行一次。
关键点:它不负责判断“要不要取消”,也不阻塞或延缓取消;它只确保你在资源还有效时,有唯一、确定、线程安全的机会做释放动作(比如 close 文件句柄、delete 临时 buffer、notify 等待方)。
如何正确构造和持有 std::stop_callback 实例
常见错误是把 std::stop_callback 声明为局部变量却期望它在异步任务结束后仍生效——它一出作用域就析构,自动注销,根本等不到取消发生。
- 必须让
std::stop_callback的生命周期覆盖整个异步任务执行期;典型做法是把它作为std::jthread或自定义任务对象的成员变量(而非栈上临时量) - 不能用
std::move后再使用原实例——std::stop_callback移动后处于无效状态,再次访问会触发未定义行为 - 回调函数捕获的变量需确保在回调执行时依然有效;避免捕获 this 指针后对象已被销毁(尤其在 lambda 中)
示例(安全写法):
class AsyncWorker {
std::jthread m_thread;
std::stop_source m_stop_source;
std::stop_callback<void> m_cleanup_cb;
public:
AsyncWorker() : m_cleanup_cb(m_stop_source, [&] {
// 这里可以安全访问 this->xxx 成员
if (m_file.is_open()) m_file.close();
delete m_buffer; // 假设 m_buffer 是 new 出来的
}) {}
void start() {
m_thread = std::jthread([this](std::stop_token token) {
while (!token.stop_requested()) {
// 工作循环
do_work();
std::this_thread::sleep_for(10ms);
}
});
}
};</void>
为什么不能在 stop_callback 里调用 request_stop 或操作同一个 stop_source
std::stop_callback 的回调执行期间,std::stop_source 已处于“停止已请求”状态,再对它调用 request_stop() 是无操作(no-op);更严重的是,如果回调里又触发了别的 request_stop()(比如转发给下游 task),可能造成死锁或重复清理。
-
std::stop_callback的语义是“响应取消”,不是“发起取消”——它的职责边界很明确 - 回调中禁止调用
m_stop_source.request_stop(),也避免向同一std::stop_token关联的其他std::stop_callback发信号 - 如果需要级联取消,应在主任务逻辑中检测
token.stop_requested()后,显式调用下游stop_source.request_stop(),而不是塞进 callback 里
std::stop_callback 和 RAII 清理的分工边界在哪
不是所有资源都该塞进 std::stop_callback。RAII 对象(如 std::unique_ptr、std::fstream)的析构函数天然适合管理自身生命周期内的资源;而 std::stop_callback 解决的是“跨协作单元”的非局部、非自动释放资源。
- 优先用 RAII:文件句柄、内存、锁 —— 如果它们的生存期与对象一致,直接靠析构就行
- 用
std::stop_callback处理:需要与取消信号强同步的动作,比如通知外部监听器、重置共享状态标志、唤醒条件变量、关闭非 RAII 封装的 C 风格资源(close(fd)) - 特别注意:不要在
std::stop_callback里释放 RAII 对象本身(如delete p后又让 unique_ptr 析构),会导致 double-free
真正容易被忽略的点是:callback 执行时机虽线程安全,但它不保证与其他线程的任意操作同步——比如另一个线程正在往同一缓冲区写数据,你在这个 callback 里清空缓冲区,必须额外加锁或用原子标记配合检查。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











