基础
让我们从一个简化的示例开始,并研究相关的Boost.Asio片段:
void handle_async_receive(...) { ... }
void print() { ... }
...  
boost::asio::io_service io_service;
boost::asio::ip::tcp::socket socket(io_service);
...
io_service.post(&print);                             
socket.connect(endpoint);                            
socket.async_receive(buffer, &handle_async_receive); 
io_service.post(&print);                             
io_service.run();                                    
什么是处理程序?
一个处理程序无非是一个回调。在示例代码中,有3个处理程序:
- 的
print处理程序(1)。 
- 的
handle_async_receive处理程序(3)。 
- 的
print处理程序(4)。 
即使print()两次使用相同的功能,也要考虑每次使用都会创建自己的唯一可识别的处理程序。处理程序可以有多种形状和大小,范围从上述基本功能到更复杂的构造,例如从boost::bind()和lambda生成的函子。不管复杂程度如何,处理程序仍然只不过是回调。
什么是工作?
工作是Boost.Asio代表应用程序代码执行的一些处理。有时Boost.Asio可能会在得知某项工作后立即开始进行某些工作,而有时它可能等待稍后的工作。完成工作后,Boost.Asio将通过调用提供的处理程序来通知应用程序。
Boost.Asio的保证了处理器将只当前调用线程中运行run(),run_one(),poll(),或poll_one()。这些是将起作用并调用处理程序的线程。因此,在上面的示例中,print()将其发布到io_service(1)中时不会被调用。而是将其添加到中io_service,并将在以后的某个时间点调用。在这种情况下,它在io_service.run()(5)之内。
什么是异步操作?
一个异步操作创建工作,Boost.Asio的将调用处理程序通知应用程序时的工作已经完成。异步操作是通过调用名称带有前缀的函数来创建的async_。这些功能也称为启动功能。
异步操作可以分解为三个独特的步骤:
- 启动或通知相关的
io_service工作需要完成。该async_receive操作(3)通知io_service,它需要异步读取数据从插座,然后async_receive立即返回。 
- 做实际的工作。在这种情况下,当
socket接收数据时,字节将被读取并复制到中buffer。实际工作将通过以下任一方式完成:
- 启动函数(3),如果Boost.Asio可以确定它不会阻塞。
 
- 当应用程序显式运行时
io_service(5)。 
 
- 调用
handle_async_receive ReadHandler。同样,处理程序仅在运行的线程内调用io_service。因此,无论工作何时完成(3或5),都可以保证handle_async_receive()仅在io_service.run()(5)内调用。 
这三个步骤之间在时间和空间上的分离称为控制流反转。这是使异步编程变得困难的复杂性之一。但是,有些技术可以帮助缓解这种情况,例如使用协程。
怎么io_service.run()办?
当线程调用时io_service.run(),将从该线程内调用work和handlers。在上面的示例中,io_service.run()(5)将一直阻塞,直到:
- 它已从两个
print处理程序调用并返回,接收操作成功或失败,并且handle_async_receive已调用并返回了其处理程序。 
- 通过
io_service明确停止了io_service::stop()。 
- 从处理程序中引发异常。
 
一种潜在的伪流可以描述如下:
创建io_service
创建套接字
将打印处理程序添加到io_service(1)
等待套接字连接(2)
向io_service添加异步读取工作请求(3)
将打印处理程序添加到io_service(4)
运行io_service(5)
  有工作或管理人员吗?
    是的,有1个工作和2个处理程序
      套接字有数据吗?不,什么都不做
      运行打印处理程序(1)
  有工作或管理人员吗?
    是的,有1个工作和1个处理程序
      套接字有数据吗?不,什么都不做
      运行打印处理程序(4)
  有工作或管理人员吗?
    是的,有1件作品
      套接字有数据吗?不,继续等待
  -套接字接收数据-
      套接字有数据,将其读入缓冲区
      将handle_async_receive处理程序添加到io_service
  有工作或管理人员吗?
    是的,有一个处理程序
      运行handle_async_receive处理程序(3)
  有工作或管理人员吗?
    否,将io_service设置为stopped然后返回
请注意如何当读完成后,它增加了一个处理程序的io_service。这个微妙的细节是异步编程的重要功能。它允许将处理程序链接在一起。例如,如果handle_async_receive未获得其期望的所有数据,则其实现可能会发布另一个异步读取操作,从而导致io_service工作量增加,因此不会从返回io_service.run()。
当待办事项io_service具有跑出的工作,应用程序必须reset()在io_service运行之前重新。
示例问题和示例3a代码
现在,让我们检查问题中引用的两段代码。
问题代码
socket->async_receive将工作添加到io_service。因此,io_service->run()它将阻塞,直到读取操作成功完成或出现错误,并且ClientReceiveEvent完成运行或引发异常为止。
为了使它更容易理解,下面是一个较小的带注释的示例3a:
void CalculateFib(std::size_t n);
int main()
{
  boost::asio::io_service io_service;
  boost::optional<boost::asio::io_service::work> work =       
      boost::in_place(boost::ref(io_service));                
  boost::thread_group worker_threads;                         
  for(int x = 0; x < 2; ++x)                                  
  {                                                           
    worker_threads.create_thread(                             
      boost::bind(&boost::asio::io_service::run, &io_service) 
    );                                                        
  }                                                           
  io_service.post(boost::bind(CalculateFib, 3));              
  io_service.post(boost::bind(CalculateFib, 4));              
  io_service.post(boost::bind(CalculateFib, 5));              
  work = boost::none;                                         
  worker_threads.join_all();                                  
}
在较高级别,程序将创建2个线程来处理io_service的事件循环(2)。这样就产生了一个简单的线程池,该线程池将计算斐波纳契数(3)。
问题代码与此代码之间的主要区别在于,该代码在实际工作和处理程序添加到(3)之前调用io_service::run()(2 )。为了防止立刻返回,创建了一个对象(1)。这个对象可以防止工作用尽。因此,不会由于没有工作而返回。io_serviceio_service::run()io_service::workio_serviceio_service::run()
总体流程如下:
- 创建并添加添加到中的
io_service::work对象io_service。 
- 创建了调用的线程池
io_service::run()。这些工作线程不会io_service因为io_service::work对象而从中返回。 
- 将3个计算斐波纳契数的处理程序添加到中
io_service,并立即返回。工作线程而不是主线程可以立即开始运行这些处理程序。 
- 删除
io_service::work对象。 
- 等待工作线程完成运行。这将仅在所有3个处理程序完成执行后发生,因为这三个处理程序都
io_service没有工作。 
可以使用与原始代码相同的方式来编写不同的代码,在原始代码中,将处理程序添加到中io_service,然后io_service处理事件循环。这样就无需使用io_service::work,从而产生以下代码:
int main()
{
  boost::asio::io_service io_service;
  io_service.post(boost::bind(CalculateFib, 3));              
  io_service.post(boost::bind(CalculateFib, 4));              
  io_service.post(boost::bind(CalculateFib, 5));              
  boost::thread_group worker_threads;                         
  for(int x = 0; x < 2; ++x)                                  
  {                                                           
    worker_threads.create_thread(                             
      boost::bind(&boost::asio::io_service::run, &io_service) 
    );                                                        
  }                                                           
  worker_threads.join_all();                                  
}
同步与异步
尽管问题中的代码正在使用异步操作,但是由于它正在等待异步操作完成,因此它可以有效地同步运行:
socket.async_receive(buffer, handler)
io_service.run();
等效于:
boost::asio::error_code error;
std::size_t bytes_transferred = socket.receive(buffer, 0, error);
handler(error, bytes_transferred);
作为一般经验法则,请尽量避免混合使用同步操作和异步操作。通常,它可以将一个复杂的系统变成一个复杂的系统。该答案强调了异步编程的优点,Boost.Asio文档中也介绍了其中的一些优点。