在现代软件开发中, 异步和并发操作是不可避免的. 然而, 当任务需要取消时, 如何优雅地终止任务, 释放资源并保持系统的稳定性, 往往是一个挑战. C++20 引入了 stop_token
, stop_source
和 stop_callback
三大工具, 为我们提供了灵活的协作式任务取消机制.
本文通过一个模拟文件下载的案例, 逐步讲解这三大工具的使用方法, 并探讨它们在任务取消中的实际应用.
任务场景: 文件下载服务
假设我们正在实现一个服务, 用于从远程服务器下载资源. 每个下载任务都可能因为以下原因中止:
- 网络错误导致的连接中断;
- 用户主动取消任务;
- 超时机制触发.
为了满足以下需求, 我们需要一个可靠的任务取消机制:
- 实时检测取消信号;
- 安全终止任务执行;
- 在取消时释放资源, 避免资源泄漏.
接下来, 我们将逐步实现这一目标.
第一幕: 基础任务取消机制
C++20 提供的 stop_source
和 stop_token
是任务取消的核心工具:
stop_source
: 用于发出取消信号;stop_token
: 任务中检测取消信号的工具.
以下示例展示了一个简单的文件下载任务如何实现基础取消功能:
#include <chrono>
#include <iostream>
#include <stop_token>
#include <thread>
using namespace std::chrono_literals;
void download_file(std::stop_token stopToken) {
int progress = 0;
while (progress < 100) {
if (stopToken.stop_requested()) {
std::cout << "[下载任务] 检测到取消请求, 停止下载.\n";
return;
}
std::this_thread::sleep_for(200ms);
progress += 1;
std::cout << "[下载任务] 当前进度: " << progress << "%\n";
}
std::cout << "[下载任务] 下载完成!\n";
}
int main() {
std::jthread downloader(download_file);
std::this_thread::sleep_for(1s);
std::cout << "[主线程] 取消下载任务.\n";
downloader.request_stop(); // 发出取消信号
return 0;
}
关键点解读
- 传递
stop_token
:stop_token
是用于任务内部检测取消信号的工具.
- 任务中检查
stop_requested
:- 使用
stop_requested()
方法可以随时响应取消请求, 确保任务安全终止.
- 使用
第二幕: 取消任务时的资源清理
在某些场景中, 任务被取消时可能需要进行资源清理, 例如关闭文件句柄或通知其他模块.
C++20 提供的 stop_callback
是实现这一需求的关键工具. 以下代码展示了如何在任务取消时自动释放资源:
#include <chrono>
#include <fstream>
#include <iostream>
#include <stop_token>
#include <thread>
using namespace std::chrono_literals;
void download_file_with_cleanup(std::stop_token token) {
std::ofstream file("output.txt", std::ios::out);
// 定义一个回调函数, 在取消时关闭文件
std::stop_callback callback(token, [&file] {
std::cout << "[下载任务]-[回调] 检测到取消请求, 关闭文件句柄.\n";
file.close();
});
int progress = 0;
while (progress < 100) {
if (token.stop_requested()) {
std::cout << "[下载任务] 检测到取消请求, 停止下载.\n";
return;
}
// 写入到文件
file << "下载进度: " << progress << "%\n";
std::cout << "[下载任务] 当前进度: " << progress << "%\n";
std::this_thread::sleep_for(200ms);
progress += 1;
}
file.close();
std::cout << "[下载任务] 下载完成, 文件已保存.\n";
}
int main() {
std::jthread downloader(download_file_with_cleanup);
std::this_thread::sleep_for(1s);
std::cout << "[主线程] 取消下载任务.\n";
downloader.request_stop();
return 0;
}
关键点解读
- 使用
stop_callback
:stop_callback
可以绑定一个回调函数, 在取消信号触发时自动执行.
- 确保资源释放:
- 在回调中关闭文件, 避免任务取消后出现资源泄漏.
第三幕: 多任务的协作取消
在实际应用中, 可能存在多个并发任务需要协同取消. 例如, 一个下载服务中同时存在多个下载任务, 共享同一个 stop_source
.
以下示例展示了如何通过共享 stop_source
实现多任务的协作取消:
#include <chrono>
#include <iostream>
#include <stop_token>
#include <syncstream>
#include <thread>
#include <vector>
using namespace std::chrono_literals;
void download_task(std::stop_token token, int id) {
int progress = 0;
while (progress < 100) {
if (token.stop_requested()) {
std::osyncstream(std::cout)
<< "[任务 " << id << "] 检测到取消请求, 停止下载.\n";
return;
}
std::this_thread::sleep_for(200ms);
progress += 10;
std::osyncstream(std::cout)
<< "[任务 " << id << "] 当前进度: " << progress << "%\n";
}
std::osyncstream(std::cout) << "[任务 " << id << "] 下载完成!\n";
}
int main() {
// 控制多个线程的退出
std::stop_source source;
std::vector<std::jthread> tasks;
for (int i = 0; i < 3; i++) {
tasks.emplace_back(download_task, source.get_token(), i);
}
std::this_thread::sleep_for(1s);
std::cout << "[主线程] 取消所有任务.\n";
source.request_stop(); // 发出取消信号
return 0;
}
关键点解读
- 共享
stop_source
:- 多个任务可以共享同一个
stop_source
, 实现统一的取消管理.
- 多个任务可以共享同一个
- 同时取消多个任务:
- 调用
request_stop()
后, 所有关联的任务都会响应取消信号.
- 调用
总结
C++20 提供的 stop_token
, stop_source
和 stop_callback
是处理任务取消的强大工具. 在异步和并发编程中, 它们可以:
- 优雅地管理任务生命周期;
- 避免资源泄漏;
- 提升系统稳定性和可维护性.
这些工具特别适合高并发服务, 实时系统和复杂任务的资源管理场景. 通过本文的示例, 希望你能更好地理解和应用 C++20 的协作取消机制, 为开发更可靠的应用奠定基础.