C++核心技术:异步编程
4 分钟阅读
•
631 字
+
384 词
异步编程的基本概念
- 允许不进行阻塞,而是让 用户代码查询操作的状态 ,
- 或 通过回调的方式 通知用户代码某个操作已经完成。
ASIO
void doA(){
doB();
doC();
}
//如果doA不需要看到doB和doC的结果,也不需要按照这个顺序执行,可以使用asio这样写
asio::io_context context;
void doA(){
context.post(doB);
context.post(doC);
}
最简单的方式是在main里调用context.post(doB);和context.run(),那样run会阻塞地运行发布到context的所有任务,直到没有任务时退出。
我们可以想象简单的io_context实现
class io_context{
public:
using task_t = function<void()>;
void post(task_t task)
{
task_queue_.push(std::move(task));
}
void run(){
for (;;) {
task_t task;
if (task_queue_. empty)) {
break;
}
task = std::move(task_queue_.front());
task_queue_pop();
task();
}
private:
queue<task_t> task_queue_;
};
实际的io_context可以支持在多个线程里同时运行。
异步运行
void sayHello(){
cout << "Hello, world! \n";
}
void doA(){
auto t = make_shared<asio::steady_timer>(context, 2s);
t-›async_wait([t] (const error_code& /*ec*/) { sayHello(); });//添加延迟执行的任务
}
//尤其要注意,下列代码是错误的
void doA(){
auto t = asio::steady_timer(context, 2s);
t.async_wait([t] (const error_code& /*ec*/) { sayHello(); });
}
如果使用这种形式的代码,运行时我们会看到 sayHello 直接运行了,没有等待两秒钟。
这是因为t在析构时会直接运行传入的函数对象
。这同时也是异步编程里一种常见的惯用法——
==对于会超出作用域的对象,我们需要用 shared_ptr 在堆上创建,并用lambda表达式捕获下来,以确保在异步执行的回调结束执行之前,用到的对象不会消失。==
另外可以注意到,我们这里异步回调有一个参数 const error_code& ec,用来表示回调的原因。对于 steady_timer的回调,它可以表示成功,也可以表示操作被中断。当 steady_timer 对象在激发之前被提前析构,或者被主动 cancel 时,ec 就会被设成某个错误码,我们可以在回调里进行检查:
t->async_wait([t] (const error_code& /*ec*/) {
if (ec == asio::error::operation aborted) {
return;
sayHello();
});