赞
踩
自工作以来一直想看boost库底层代码,但每次都被一大堆宏以及各种模板劝退了,这段时间不怎么忙,系统学习了template后,还是坚持看完了。学习过程中发现有关这部分文章比较少,大多也只是泛泛而谈,所以在此分享下,希望能帮助到大家,如有问题欢迎评论。
我的boost版本为1.7.5
boost版本可以通过引入boost/version.hpp,然后点进去查看
boost不同于libevent或者muduo ,使用的为前摄器模式。
前摄器和reactor模式很类似,区别在于:reactor是当socket可读时,调用用户回调,由用户调用系统调用读。而前摄器模式是当socket可读时,将数据读取,然后在合适时机,调用用户回调来处理数据。由此可见两者最大的区别在于,用户回调中是否需要io操作。相比reactor,前摄器响应io速度会更快,但回调触发的实时性会弱于reactor,以及实现复杂度也会相比reactor更高。
asio 中主要概念如下
excutor : 如io_contex和io_service,负责任务执行和调度。
operation: 任务类,分为io任务(网络和定时器)和普通函数任务。
service: 提供服务,可以理解为代理类,做不同任务添加的 添加或者修改。
首先我们看下io_context的类图关系,可见io_context,继承于execution_context, execution_context中包含service_register, service_register用于注册service,然后将service以头插法的方式构成service链表。
先看下service类关系。
schduler是 任务调度的实现类
reactive_socket_service 用于提供socket相关操作
模板类execution_context_service_base中仅有一个静态数据成员id,模板参数使用子类类型实例化。
由此可见我们可以总结出service用于提供逻辑操作。
operation也就是我上面说的任务,为scheduler_operation的typedef
descriptor_state 用于和文件描述符绑定,在内部有read,write,err队列,在设置文件描述符监听事件时传入,待epoll返回后从返回事件的ptr中取出,根据返回类型触发不同的io操作。
reactor_op 为io操作任务,该类对象直接添加到descriptor的队列中。reactor_op中有两个函数数据成员:1个用于执行对应的io任务,如读写;另一个保存用户传入的回调,在适当时机将之前io操作的结果传回调。
task_operation 也继承同一基类,该类没有过多作用,仅仅用于做为一个op_quque队尾标记使用。
有了上面类关系梳理,我们下面走流程。顺序为先任务调度,再任务添加。你也可以先看任务添加再看任务调度。
任务调度(事件循环),下面以io_context为例,单线程为例(即一个io_context仅bind一个thread)。
假设我们使用场景为 : 创建io_context对象ioc_ --> 将ioc_.run()运行再另外其一个线程中。
流程分两步
- 调用基类构造函数创建service_register
- 利用service_register创建scheduler ,此时service_register的service链表中插入了第一个service
thread_info 为每个线程的独享变量,内部有private_op_queue, 用于临时存储触发事件的descriptor_state,以及完成io读写的reactor_op
context 为上下文,以链表形式存储context对象,context 的 id为schedule对象的地址,value为this_info , 所以可以根据一个io_context 找到它所有的thread_info,也即可以很容易却修改不同线程的局部operation队列 private_op_queue。
2)循环调用do_run_one来消费op_queue
代码
[注] : op_queue为scheduler的成员变量
std::size_t scheduler::do_run_one(mutex::scoped_lock& lock, scheduler::thread_info& this_thread, const boost::system::error_code& ec) { while (!stopped_) { if (!op_queue_.empty()) { // Prepare to execute first handler from queue. operation* o = op_queue_.front(); op_queue_.pop(); bool more_handlers = (!op_queue_.empty()); if (o == &task_operation_) { task_interrupted_ = more_handlers; if (more_handlers && !one_thread_) wakeup_event_.unlock_and_signal_one(lock); else lock.unlock(); task_cleanup on_exit = { this, &lock, &this_thread }; (void)on_exit; // Run the task. May throw an exception. Only block if the operation // queue is empty and we're not polling, otherwise we want to return // as soon as possible. task_->run(more_handlers ? 0 : -1, this_thread.private_op_queue); } else { std::size_t task_result = o->task_result_; if (more_handlers && !one_thread_) wake_one_thread_and_unlock(lock); else lock.unlock(); // Ensure the count of outstanding work is decremented on block exit. work_cleanup on_exit = { this, &lock, &this_thread }; (void)on_exit; // Complete the operation. May throw an exception. Deletes the object. o->complete(this, ec, task_result); this_thread.rethrow_pending_exception(); return 1; } } else { wakeup_event_.clear(lock); wakeup_event_.wait(lock); } } return 0; }
伪代码
while(!stop){ if(op_queue非空){ //从op_queue中出队一个operation if(/*该operation为队尾标志*/){ if(/*io_context绑定多个线程*/){ //唤醒一个线程 }else{ //解锁 } if(/*op_queue空*/){ //reactor堵塞 }else{ reactor立刻返回 } //将reactor返回的operation添加到op+queue中 }else{ //从op中获取执行结果(注意io操作,第一次结果为触发事件类型,第二次为io操作结果) //调用op的完成函数(第一次为根据返回事件类型读写数据,第二次为将读写的结果传给用户回调) } }else{ //让渡出cpu } }
reactor
在linux系统下为epoll,在windows下为io_cp
reactor.run(),删减了定时器相关逻辑
void epoll_reactor::run(long usec, op_queue<operation>& ops) { // Block on the epoll descriptor. epoll_event events[128]; int num_events = epoll_wait(epoll_fd_, events, 128, timeout); for (int i = 0; i < num_events; ++i) { void* ptr = events[i].data.ptr; if (ptr == &interrupter_) { else { // The descriptor operation doesn't count as work in and of itself, so we // don't call work_started() here. This still allows the scheduler to // stop if the only remaining operations are descriptor operations. descriptor_state* descriptor_data = static_cast<descriptor_state*>(ptr); if (!ops.is_enqueued(descriptor_data)) { descriptor_data->set_ready_events(events[i].events); ops.push(descriptor_data); } else { descriptor_data->add_ready_events(events[i].events); } } } }
reactor.run()封装了epoll,并将触发了事件的文件描述符fd绑定的descriptor_state,传给thread_info的private_op_queue,本次循环结束后添加到op_queue中。
完整流程如下:
异步任务执行逻辑流程图
向io_context添加任务的方式大致有三种
如async_wirte,async_accept之类
定时器 : 定时器的实现方式和muduo库类似,感兴趣可以直接看muduo库学习下
post() post添加的为普通函数任务
使用过boost编写过网络程序,对下面这三行一定不陌生
socket 用于读写数据
acceptor 用于接受连接
resolver用于域名解析(dns解析)
typedef basic_stream_socket<tcp> socket
typedef basic_socket_acceptor<tcp> acceptor
typedef basic_resolver<tcp> resolver;
下面将会通过acceptor来阐述异步io任务添加的全过程,这部分也是个人感觉比较难懂的部分,可能内办法介绍出每一个细节,做好还是能够结合我注解亲自看看代码。
basic_socket_acceptor,内部成员为io_object_impl。io_object_impl使用reactive_socket_service和excutor类型实例化模板。
流程分两部分
acceptor构造函数
我们可以看知道这里完成了impl的初始化,然后创建了socket用于等待连接。
open : 创建socket_fd,创建descriptor_state,完成scoket_fd和descriptor的绑定以及向epoll监视集合中的注册。
bind: 绑定ip端口
listen :赋予socket监听能力
io_object_impl构造类
IoObjectService<Service , execution>为reactive_socket_service< tcp >
首先利用user_sevice()从传入的io_context的service_regitser获取reactive_socket_service< tcp >,如果没有则创建并添加到service_regitser中。然后调用reactive_socket_service<>基类方法初始化implemetation_,初始状态为无效状态。
implemtation 类定义
acceptor初始化完毕后,我们需要调用async_accept开始异步accept。
1部分为模板参数列表,简单来说就是template< typename MoveAcceptHandler = void >
2部分 为 auto,返回值类型为return返回值的类型。
handler为bind返回的函数对象(用户传入回调的函数对象)
调用aync_initiate 创建 initiate_async_move_accept对象(该对象负责添加异步任务逻辑)
async_initiate函数
Initiation : 为 initiate_async_move_accept之类用于做事件分发的
ComletetionToken : 我们可以简单理解为用户回调。
上面completion作用暂时不是很清楚,只知道包裹用户回调,建立异步回调和异步结果的联系。
方框标出部分实现逻辑为(1)调用initiation的转移构造函数后,(2)调用initiation重载后的()运算符。
该例子中调用 initiate_async_move_accept的()运算符
impl_为最开始介绍的io_object_impl
get_service()获得的为reactive_socket_service
所以接下来调用时reactive_socket_service< tcp >下的async_accept
1部分 : 创建reactive_socket_accept_op对象,实际上在该对象中保存了2个函数,(1)执行io操作的函数,(2)是用户传入的回调
reactive_socket_accept_op 多层继承,最上层基类scheduler_operation 类定义大致如下:
(1) reactive_socket_Accept_base基类构造
(2)
至此我们可以看出,用户回调保存在顶级基类operation中,执行io操作的回调函数保存在perform_func中。
2部分 : 添加该opeation到对应的socket_fd所绑定的descriptor_state的读操作队列中。
该代码逻辑为 : 如果peer_is_open ,则直接填入error_code,将该operation 通过reactor_添加到io_context的op_queue中,由事件循环直接执行用户回调;
如果peer_is_open为false, 调用reactor_op添加该opeation到对应的socket_fd所绑定的 descriptor_state的read操作队列中。
start_op代码
至此async_Accept 添加任务结束,等待客户端连接到达触发用户回调。
Post是asio最常用的函数之一,可以将普通函数任务添加到eventloop中。这部分流程不复杂,主要难点是模板相关的内容。下面为了尽可能详细,可能导致条理不是特别清晰,希望大家能给提点建议,帮助后续优化文章结构。
这里和上文一样,就是创建initiate_post对象,然后调用initiate_post的operator(),将handler,以及this指针作为传入参数。
代码如下
代码大致上可以分为三部分
- 1部分以handler作为传入对象,创建非const属性的handler2
- 2部分根据handler类型实例化operation类型
- 3部分将该operation添加到eventloop的任务队列中。
下面对这三部分展开讲
上面拷贝构造函数中实际上就是对传入参数t进行了强制类型转换(如果T为引用,则强转为T的右值引用,否则强转为T的引用)
- 1为conditional主模板
- 2 为conditional模板类的偏特化模板
当我们实例化模板conditional时,如果 _Cond 是false,则匹配偏特化版本,则该condital类实例化类的type为第三个类型参数*** _Iffalse***;_Cond 非false,则匹配主模板,type为第二个类型参数*** _Iftrue***。
上面我们可以知道当我们用两个两个同样的类型实例化is_same时,is_same匹配ture_type;其他情况匹配false_type
ture_type,false_type 为使用如下类型参数实例化 integral_constant模板的typedef。
这段代码考察了非类型模板参数
第一个方框:类型参数_Tp
第二个方框 : 实例化时需要填入一个_Tp的值
所以上文false_type::value 为false。
用于退化类型,如T 为int&,我们std::decay< int& >::type 得到类型int。
详细介绍看下面链接
std::decay链接
完整代码如下
template <typename Handler, typename IoExecutor> class completion_handler : public operation { public: // ptr类定义 BOOST_ASIO_DEFINE_HANDLER_PTR(completion_handler); completion_handler(Handler& h, const IoExecutor& io_ex) : operation(&completion_handler::do_complete), handler_(BOOST_ASIO_MOVE_CAST(Handler)(h)), work_(handler_, io_ex) { } static void do_complete(void* owner, operation* base, const boost::system::error_code& /*ec*/, std::size_t /*bytes_transferred*/) { // Take ownership of the handler object. completion_handler* h(static_cast<completion_handler*>(base)); ptr p = { boost::asio::detail::addressof(h->handler_), h, h }; BOOST_ASIO_HANDLER_COMPLETION((*h)); // Take ownership of the operation's outstanding work. handler_work<Handler, IoExecutor> w( BOOST_ASIO_MOVE_CAST2(handler_work<Handler, IoExecutor>)( h->work_)); Handler handler(BOOST_ASIO_MOVE_CAST(Handler)(h->handler_)); p.h = boost::asio::detail::addressof(handler); p.reset(); // Make the upcall if required. if (owner) { fenced_block b(fenced_block::half); BOOST_ASIO_HANDLER_INVOCATION_BEGIN(()); w.complete(handler, handler); BOOST_ASIO_HANDLER_INVOCATION_END; } } private: Handler handler_; handler_work<Handler, IoExecutor> work_; };
struct ptr是completion_handler辅助类,作用是根据completion_handler绑定的handler类型选择不同的allocator为completion_handler开辟一段空间,但不使用构造函数。下面将详细介绍allocate实现。
展开代码如下
struct ptr \ { \ Handler* h; \ op* v; \ op* p; \ ~ptr() \ { \ reset(); \ } \ static op* allocate(Handler& handler) \ { \ typedef typename ::boost::asio::associated_allocator< \ Handler>::type associated_allocator_type; \ typedef typename ::boost::asio::detail::get_hook_allocator< \ Handler, associated_allocator_type>::type hook_allocator_type; \ BOOST_ASIO_REBIND_ALLOC(hook_allocator_type, op) a( \ ::boost::asio::detail::get_hook_allocator< \ Handler, associated_allocator_type>::get( \ handler, ::boost::asio::get_associated_allocator(handler))); \ return a.allocate(1); \ } \ void reset() \ { \ if (p) \ { \ p->~op(); \ p = 0; \ } \ if (v) \ { \ typedef typename ::boost::asio::associated_allocator< \ Handler>::type associated_allocator_type; \ typedef typename ::boost::asio::detail::get_hook_allocator< \ Handler, associated_allocator_type>::type hook_allocator_type; \ BOOST_ASIO_REBIND_ALLOC(hook_allocator_type, op) a( \ ::boost::asio::detail::get_hook_allocator< \ Handler, associated_allocator_type>::get( \ *h, ::boost::asio::get_associated_allocator(*h))); \ a.deallocate(static_cast<op*>(v), 1); \ v = 0; \ } \ } \ } \
associated_allocator::get作用:如果函数对象handler中有定义自己的allocator则获得该类型的allocator对象,否则返回std::allocate< void > 对象。模板associated_allocator_impl为associated_allocator的实现。
template <typename T, typename E, typename = void> struct associated_allocator_impl { typedef E type; static type get(const T&, const E& e) BOOST_ASIO_NOEXCEPT { return e; } }; //模板第三方参数为void,且T中定义了allocator_type,则走偏特化版本 template <typename T, typename E> struct associated_allocator_impl<T, E, typename void_type<typename T::allocator_type>::type> { typedef typename T::allocator_type type; static type get(const T& t, const E&) BOOST_ASIO_NOEXCEPT { return t.get_allocator(); } }; //associated_allocator模板Allocator默认类型参数为std::allocator<void> template <typename T, typename = std::allocator<void> > struct associated_allocator { typedef typename detail::associated_allocator_impl<T, Allocator>::type type; static type get(const T& t, const Allocator& a = Allocator()) BOOST_ASIO_NOEXCEPT { return detail::associated_allocator_impl<T, Allocator>::get(t, a); } };
上文可见调用如下 detail::associated_allocator_impl<T, Allocator>::get(t, a)
可见第一个类型参数为T,第二个参数为Allocator。优先去尝试匹配associated_allocator_impl的偏特化版本,代码中可见第三个参数为void_type< typename T::allocator_type>::type>, void_type<>::type 类型始终为void。如果T有allocator_type类型,则associated_allocator_impl实例化为偏特化版本,否则由于sfinae丢弃偏特化版本,选择主模板版本。
associated_allocator::type : 如果handler定义了allocator_type类型,则为handler::handler;否则为
std::allocator< void >
get_hook_allocator为利用模板实现的一个hook。
//hook_allocator主模板 template <typename Handler, typename T> class hook_allocator { public: typedef T value_type; template <typename U> struct rebind { typedef hook_allocator<Handler, U> other; }; explicit hook_allocator(Handler &h) : handler_(h) { } template <typename U> hook_allocator(const hook_allocator<Handler, U> &a) : handler_(a.handler_) { } T *allocate(std::size_t n) { return static_cast<T *>( boost_asio_handler_alloc_helpers::allocate(sizeof(T) * n, handler_)); } void deallocate(T *p, std::size_t n) { boost_asio_handler_alloc_helpers::deallocate(p, sizeof(T) * n, handler_); } // private: Handler &handler_; }; //hook_allocator偏特化版本 // 类型void不能够分配内存,所以该版本不应该有allocate和deallocate方法 template <typename Handler> class hook_allocator<Handler, void> { public: typedef void value_type; template <typename U> struct rebind { typedef hook_allocator<Handler, U> other; }; explicit hook_allocator(Handler &h) : handler_(h) { } template <typename U> hook_allocator(const hook_allocator<Handler, U> &a) : handler_(a.handler_) { } // private: Handler &handler_; }; template <typename Handler, typename Allocator> struct get_hook_allocator { typedef Allocator type; static type get(Handler &, const Allocator &a) { return a; } }; template <typename Handler, typename T> struct get_hook_allocator<Handler, std::allocator<T>> { typedef hook_allocator<Handler, T> type; static type get(Handler &handler, const std::allocator<T> &) { return type(handler); } };
上面代码我们可以看到get_hook_allocator 偏特化版本是当第二个类型参数为std::allocate< T >时, 用T作为一个类型参数实例化hook_allocator模板。如果此时我们使用std::allocator< void >作为get_hook_allocator 的第二个类型参数,则匹配hook_allocator对应其偏特化版本。
在上面这些基础上我们,走一遍completion_handler::ptr的allocate流程。
Handler为std::bind返回的可调度对象,无内置allocator类型和excutor_type。
- associated_allocator_type 为std::allocator< void >
- hook_allocator_type 为 hook_allocator<Handler, void>
- BOOST_ASIO_REBIND_ALLOC(hook_allocator_type, op)创建hook_allocator<Handler, void>::rebind::hook_allocator<Handler, U> ,U为 completion_handler 的对象a。
- 调用a.allocate()即可获得completion_handler大小的内存。
T为completion_handler,该allocate返回一个开辟的内存地址。
thread_info,在调用run时添加到thread_info栈中
detail::thread_context::top_of_thread_call_stack()找到栈顶的thread_info
看到下面这部分代码可能会不清楚设计目的。这里是boost的内存管理,既减少了new小内存块产生的内存碎片;又复用了内存,减少了底层malloc的调用次数,可以说是一举两得。
内存分配具体代码如下:
伪代码如下
template <typename Purpose> static void* allocate(Purpose, thread_info_base* this_thread, /*分配字节数size*/) { //chunks 为boost自定义一个内存块单元 //下面代码为计算所需chunk的数量(方式为向上取整) std::size_t chunks = (size + chunk_size - 1) / chunk_size; if (/*this_thread->reusable_memory_对应purpose分配了内存*/) { if (/*需要的字节数size<=已经分配的内存大小*/) { //返回这块内存地址 } /*需要的字节数size > 已经分配的内存大小*/ //删除该内存 } //根据chunk数开配对应大小字节内存,返回内存地址 }
template <typename Purpose> static void deallocate(Purpose, thread_info_base* this_thread, void* pointer, std::size_t size) { if (/*pointer指向内存size 合适*/ ) { if (/*this_thread->reusable_memory_[Purpose::mem_index]未分配内存*/) { //将这部分内存交给this_thread->reusable_memory_[Purpose::mem_index] return; } } /*pointer指向内存size 过大*/ //直接删除pointer指向内存 }
接下来我们回到io_context::initiate_post中
这里我们可以创建了ptr对象,以及分配了completion_handler大小的小并存储再p.v中,然后调用new对p.v使用completion_handler的构造函数初始化内存,内存地址赋值给p.p,至此一个包装handler为operation对象完毕。
添加operation对象对象到sheduler中
根据代码可知,当evnetloop为单线程情况下,该operation对象直接添加到this_thread->private_op_Queue中,多线程情况下添加到op_queue中
至此post流程结束,普通函数任务添加到了eventloop中。
上文已经将包裹函数对象handler的operation添加到了eventloop中。eventloop处理operation,调用completion_handler.do_complete方法。
构造completion_handler时,我们同时初始化了成员handler_work<Handler, IoExecutor> work_,handler_work的作用是如果Handler中没定义executor_type类型,则直接调用用户回调。
static void do_complete(void* owner, operation* base, const boost::system::error_code& /*ec*/, std::size_t /*bytes_transferred*/) { // 对base类型恢复成completion_handler对象指针 completion_handler* h(static_cast<completion_handler*>(base)); ptr p = { boost::asio::detail::addressof(h->handler_), h, h }; BOOST_ASIO_HANDLER_COMPLETION((*h)); // 右值引用知识点,使用w获取h->work对象的控制权 handler_work<Handler, IoExecutor> w( BOOST_ASIO_MOVE_CAST2(handler_work<Handler, IoExecutor>)( h->work_)); // 同上 Handler handler(BOOST_ASIO_MOVE_CAST(Handler)(h->handler_)); p.h = boost::asio::detail::addressof(handler); //释放内存空间 p.reset(); if (owner) { //调用回调,执行用户回调 w.complete(handler, handler); } }
对该模板还有部分没有搞清楚如何走dispatch分支。经过debug测试std::bind返回的handler不走dispatch分支。后续研究清楚再详细更新内容。
//主模板 template <typename Handler, typename IoExecutor, typename = void> class handler_work : handler_work_base<IoExecutor>, handler_work_base<typename associated_executor< Handler, IoExecutor>::type, IoExecutor> { public: typedef handler_work_base<IoExecutor> base1_type; typedef handler_work_base<typename associated_executor< Handler, IoExecutor>::type, IoExecutor> base2_type; handler_work(Handler& handler, const IoExecutor& io_ex) BOOST_ASIO_NOEXCEPT : base1_type(0, 0, io_ex), base2_type(boost::asio::get_associated_executor(handler, io_ex), io_ex) { } template <typename Function> void complete(Function& function, Handler& handler) { if (!base1_type::owns_work() && !base2_type::owns_work()) { boost_asio_handler_invoke_helpers::invoke(function, handler); } else { base2_type::dispatch(function, handler); } } }; //偏特化版本 template <typename Handler, typename IoExecutor> class handler_work< Handler, IoExecutor, typename enable_if< is_same< typename associated_executor<Handler, IoExecutor>::asio_associated_executor_is_unspecialised, void >::value >::type> : handler_work_base<IoExecutor> { public: typedef handler_work_base<IoExecutor> base1_type; handler_work(Handler&, const IoExecutor& io_ex) BOOST_ASIO_NOEXCEPT : base1_type(0, 0, io_ex) { } template <typename Function> void complete(Function& function, Handler& handler) { if (!base1_type::owns_work()) { boost_asio_handler_invoke_helpers::invoke(function, handler); } else { base1_type::dispatch(function, handler); } } };
completion_handler成员变量 work_,实例化handler_work<Handler, IoExecutor>,该实例匹配偏特化版本
//主模板 template <typename Executor, typename CandidateExecutor = void, typename IoContext = io_context, typename PolymorphicExecutor = executor, typename = void> class handler_work_base { public: explicit handler_work_base(int, int, const Executor& ex) BOOST_ASIO_NOEXCEPT : executor_(boost::asio::prefer(ex, execution::outstanding_work.tracked)) { } template <typename OtherExecutor> handler_work_base(const Executor& ex, const OtherExecutor&) BOOST_ASIO_NOEXCEPT : executor_(boost::asio::prefer(ex, execution::outstanding_work.tracked)) { } handler_work_base(const handler_work_base& other) BOOST_ASIO_NOEXCEPT : executor_(other.executor_) { } #if defined(BOOST_ASIO_HAS_MOVE) handler_work_base(handler_work_base&& other) BOOST_ASIO_NOEXCEPT : executor_(BOOST_ASIO_MOVE_CAST(executor_type)(other.executor_)) { } #endif // defined(BOOST_ASIO_HAS_MOVE) bool owns_work() const BOOST_ASIO_NOEXCEPT { return true; } template <typename Function, typename Handler> void dispatch(Function& function, Handler& handler) { execution::execute( boost::asio::prefer(executor_, execution::blocking.possibly, execution::allocator((get_associated_allocator)(handler))), BOOST_ASIO_MOVE_CAST(Function)(function)); } private: typedef typename decay< typename prefer_result<Executor, execution::outstanding_work_t::tracked_t >::type >::type executor_type; executor_type executor_; }; //偏特化版本 template <typename Executor, typename IoContext, typename PolymorphicExecutor> class handler_work_base<Executor, void, IoContext, PolymorphicExecutor, typename enable_if< is_same< Executor, typename IoContext::executor_type >::value >::type> { public: explicit handler_work_base(int, int, const Executor&) { } bool owns_work() const BOOST_ASIO_NOEXCEPT { return false; } template <typename Function, typename Handler> void dispatch(Function& function, Handler& handler) { boost_asio_handler_invoke_helpers::invoke(function, handler); } };
在这里我们可以暂时得出结论:如可调度对象没有定义executor_type类型的话,complete_handler::do_complete 始终调用boost_asio_handler_invoke_helpers::invoke(function, handler),即立刻调用用户传入回调。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。