等待助推器asio的未来在io_service.stop()之后永远持续下去

Waiting boost asio's future lasts forever after io_service.stop()

本文关键字:之后 永远 stop service asio 助推器 未来 io 等待      更新时间:2023-10-16

我尝试等待从任何boost::asio::async_函数返回的std::future对象(使用 use_future)。例如:

auto endpoint_future = resolver.async_resolve(query, boost::asio::use_future);
endpoint_future.wait(); // lasts forever after io_service.stop();

这是在一个线程中完成的。我还有另一个线程在此async_resolve调用之前启动:

runner_ = std::thread([this]() {
    boost::asio::io_service::work work(io_service_);
    io_service_.run();
});

一切正常,但后来我还添加了一个boost::asio::deadline_timer来停止任何与io_service的工作:

void deadlineTimeout() {
    deadline_.cancel();
    io_service_.stop();
    // closing socket here does not work too
}

但是,deadlineTimeout()当截止日期达到时,它会超时并且它会执行io_service_.stop()未来没有被释放,因此endpointer_future.wait()仍然阻塞。在这种情况下,我怎样才能停止等待未来?

我自己找到了一个解决方案:我们不需要stop() io_service而是reset()它,在此之前我们需要关闭套接字,因此正确的超时回调将是:

void deadlineTimeout() {
    deadline_.cancel();
    socket_.close(); // socket_ is a socket on io_service_
    io_service_.reset();
}

在此更改之后,所有期货都将被释放。

io_sevice.stop()的调用将导致所有run()run_one()的调用尽快返回。 从处理程序中调用时,调用方将从run()返回,而不调用任何其他处理程序。 在您的情况下,async_resolve 的完成处理程序将设置与endpoint_future关联的promise;但是,通过停止io_service,将不会调用完成处理程序。 请考虑以下任一情况:

  • cancel()future关联的 I/O 对象,然后继续运行io_service直至完成,以便设置promise的值
  • 销毁所有 I/O 对象,然后销毁io_service以便删除处理程序并由future检测到已损坏的承诺
  • 循环
  • 对未来执行定时等待,如果未来已准备就绪或io_service已停止,则退出循环。 例如,以下函数返回一个boost::optional,其中包含未来或boost::none 如果不会设置未来,则返回该值。

    template <typename T>
    boost::optional<T> get(
      boost::asio::io_service& io_service,
      std::future<T>& future)
    {
      for (;;)
      {
        // If the future is ready, get the value.
        if (future.wait_for(std::chrono::seconds(1)) == std::future_status::ready)
        {
          return {future.get()};
        }
        // Otherwise, if the future is never going to be set, return none.
        if (io_service.stopped())
        {
          return {boost::none};
        }
      }
    }
    ...
    if (auto endpoint_iterator = get(io_service, endpoint_future))
    {
      // use *endpoint_iterator...
    }
    

下面是一个示例,演示如何在停止io_service的同时安全地等待未来:

#include <chrono>
#include <iostream>
#include <thread>
#include <boost/asio.hpp>
#include <boost/asio/use_future.hpp>
#include <boost/optional.hpp>
template <typename T>
boost::optional<T> get(
  boost::asio::io_service& io_service,
  std::future<T>& future)
{
  for (;;)
  {
    // If the future is ready, get the value.
    if (future.wait_for(std::chrono::seconds(1)) == std::future_status::ready)
    {
      return {future.get()};
    }
    // Otherwise, if the future is never going to be set, return none.
    if (io_service.stopped())
    {
      std::cout << "io_service stopped, future will not be set" << std::endl;
      return {boost::none};
    }
    std::cout << "timeout waiting for future" << std::endl;
  }
}
int main()
{
  boost::asio::io_service io_service;
  // Create I/O objects.
  boost::asio::ip::udp::socket socket(io_service,
    boost::asio::ip::udp::v4());
  boost::asio::deadline_timer timer(io_service);
  // Process the io_service in the runner thread.
  auto runner = std::thread([&]() {
    boost::asio::io_service::work work(io_service);
    io_service.run();
  });
  // Start an operation that will not complete.
  auto bytes_transferred_future = socket.async_receive(
    boost::asio::null_buffers(), boost::asio::use_future);
  // Arm the timer.
  timer.expires_from_now(boost::posix_time::seconds(2));
  timer.async_wait([&](const boost::system::error_code&) {
    timer.cancel();
    socket.close();
    io_service.stop();
  });
  // bytes_transferred's promise will never be set as the io_service
  // is not running.
  auto bytes_transferred = get(io_service, bytes_transferred_future);
  assert(!bytes_transferred);
  runner.join();
}

使工作在截止时间超时时可见,并允许截止时间超时将其删除。

boost::scoped_ptr<boost::asio::io_service::work work;

您仍然可以在此处创建工作,但要分配堆。

runner_ = std::thread([this]() {
    work = new boost::asio::io_service::work(io_service_);
    io_service_.run();
});

并像这样关闭:

void deadlineTimeout() {
    deadline_.cancel();
    socket_.close(); // socket_ is a socket on io_service_
    work.reset();
    // The io_service::run() will exit when there are no active requests 
    // (i.e. no sockeet, no deadline, and no work)
    // so you do not need to call: io_service_.stop();
}