asio boost 异步错误处理_当boost :: asio :: io_service运行方法阻塞/取消阻塞时感到困惑...

基础

让我们从一个简化的示例开始,并研究相关的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);                             // 1

socket.connect(endpoint);                            // 2

socket.async_receive(buffer, &handle_async_receive); // 3

io_service.post(&print);                             // 4

io_service.run();                                    // 5

什么是处理程序?

一个处理程序只不过是一个回调多。在示例代码中,有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(),将从该线程内调用工作和处理程序。在上面的示例中,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代码

为了使读者更容易理解,下面是一个较小的带注释的示例3a:

void CalculateFib(std::size_t n);

int main()

{

boost::asio::io_service io_service;

boost::optional<:asio::io_service::work> work =       // '. 1

boost::in_place(boost::ref(io_service));                // .'

boost::thread_group worker_threads;                         // -.

for(int x = 0; x < 2; ++x)                                  //   :

{                                                           //   '.

worker_threads.create_thread(                             //     :- 2

boost::bind(&boost::asio::io_service::run, &io_service) //   .'

);                                                        //   :

}                                                           // -'

io_service.post(boost::bind(CalculateFib, 3));              // '.

io_service.post(boost::bind(CalculateFib, 4));              //   :- 3

io_service.post(boost::bind(CalculateFib, 5));              // .'

work = boost::none;                                         // 4

worker_threads.join_all();                                  // 5

}

在较高级别,程序将创建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));              //   :- 3

io_service.post(boost::bind(CalculateFib, 5));              // .'

boost::thread_group worker_threads;                         // -.

for(int x = 0; x < 2; ++x)                                  //   :

{                                                           //   '.

worker_threads.create_thread(                             //     :- 2

boost::bind(&boost::asio::io_service::run, &io_service) //   .'

);                                                        //   :

}                                                           // -'

worker_threads.join_all();                                  // 5

}

同步与异步

尽管问题中的代码使用异步操作,但是由于它正在等待异步操作完成,因此它可以有效地同步运行:

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 文档中也介绍了其中的一些优点。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值