在C++异步编程场景中,多个有依赖关系的异步操作如果采用嵌套回调的方式实现,会出现层层嵌套的回调地狱问题,代码可读性和维护性都会大幅下降。通过设计可串联的异步回调任务链,可以让异步任务的执行逻辑更线性,解决嵌套问题。

核心设计思路
可串联的异步回调任务链的核心是将每个异步任务封装为独立的节点,每个节点包含自身的异步执行逻辑和下一个任务的引用,当当前任务执行完成后,自动触发下一个任务的执行。同时提供链式调用的接口,让开发者可以像写同步代码一样拼接异步任务。
任务节点封装
首先定义一个任务基类,所有具体的异步任务都继承自这个基类,基类需要包含执行任务和设置下一个任务的方法:
#include <functional>
#include <memory>
#include <iostream>
#include <thread>
#include <chrono>
// 任务基类,定义任务的基本接口
class AsyncTask {
public:
virtual ~AsyncTask() = default;
// 执行当前任务,传入任务完成后的回调
virtual void execute(std::function<void()> on_finish) = 0;
// 设置下一个要执行的任务
void set_next(std::shared_ptr<AsyncTask> next_task) {
next_ = next_task;
}
// 获取下一个任务
std::shared_ptr<AsyncTask> get_next() const {
return next_;
}
private:
std::shared_ptr<AsyncTask> next_;
};
具体异步任务实现
继承基类实现具体的异步任务,每个任务内部模拟异步操作,完成后调用传入的回调,回调中触发下一个任务的执行:
// 具体的异步任务实现,模拟耗时操作
class ConcreteTask : public AsyncTask {
public:
// 构造函数传入任务标识和模拟耗时(毫秒)
ConcreteTask(std::string task_name, int delay_ms)
: task_name_(task_name), delay_ms_(delay_ms) {}
void execute(std::function<void()> on_finish) override {
std::cout << task_name_ << " 开始执行,模拟耗时 " << delay_ms_ << " 毫秒" << std::endl;
// 模拟异步操作,使用线程休眠代替实际异步逻辑
std::thread([this, on_finish]() {
std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms_));
std::cout << task_name_ << " 执行完成" << std::endl;
// 当前任务完成后,先执行传入的完成回调
if (on_finish) {
on_finish();
}
// 触发下一个任务的执行
auto next = get_next();
if (next) {
next->execute([](){});
}
}).detach();
}
private:
std::string task_name_;
int delay_ms_;
};
任务链封装与链式调用
为了更方便地拼接任务,我们可以封装一个任务链管理类,提供链式调用的接口,让任务拼接更符合使用习惯:
// 任务链管理类,提供链式调用接口
class TaskChain {
public:
// 添加任务到链中
TaskChain& add_task(std::shared_ptr<AsyncTask> task) {
if (!head_) {
head_ = task;
tail_ = task;
} else {
tail_->set_next(task);
tail_ = task;
}
return *this;
}
// 启动任务链执行
void start() {
if (head_) {
head_->execute([](){});
}
}
private:
std::shared_ptr<AsyncTask> head_;
std::shared_ptr<AsyncTask> tail_;
};
使用示例
下面是使用上述任务链实现多个异步任务串联执行的示例,对比传统嵌套回调的方式,代码结构更清晰:
int main() {
// 创建任务链并添加三个异步任务
TaskChain chain;
chain.add_task(std::make_shared<ConcreteTask>("任务1", 1000))
.add_task(std::make_shared<ConcreteTask>("任务2", 1500))
.add_task(std::make_shared<ConcreteTask>("任务3", 800));
// 启动任务链
std::cout << "任务链启动" << std::endl;
chain.start();
// 主线程等待所有任务完成,避免程序提前退出
std::this_thread::sleep_for(std::chrono::seconds(4));
return 0;
}
优势说明
这种实现方式的优势在于:
- 每个异步任务独立封装,职责单一,便于单独测试和修改
- 任务链通过链式调用拼接,执行顺序一目了然,避免了多层嵌套的回调地狱
- 扩展性强,后续如果需要添加任务优先级、错误处理等功能,只需要在基类和任务链类中扩展即可
如果需要在任务之间传递数据,可以修改execute方法的回调参数,增加数据传递的接口,比如将回调定义为std::function<void(const std::string&)>,在任务完成时传递结果给下一个任务。