suzuiyue
suzuiyue

Reputation: 156

boost::asio::strand::dispatch(handle) or call the handle directly?

I am new to boost::asio. The boost doc said:

The strand object guarantees that handlers posted or dispatched through the strand will not be executed concurrently. The handler may be executed inside this function if the guarantee can be met. If this function is called from within a handler that was posted or dispatched through the same strand, then the new handler will be executed immediately.

So I want to know when the new handler will be executed immediately, why not just direct call it instead of use dispatch?

Upvotes: 2

Views: 2096

Answers (1)

Tanner Sansbury
Tanner Sansbury

Reputation: 51961

If the caller always runs within the strand, then one can invoke the handler directly. Otherwise, if the caller does not always run within the strand, then one can use strand.dispatch() to meet concurrency requirements while potentially optimizing some call chains.

For example, consider the case where an application protocol requires a heartbeat to be sent on both a periodic and in response to every heartbeat request. One may design the asynchronous call chains so that there are two discrete call chains:

  • a keepalive class is responsible for a call chain that periodically sends a heartbeat. It will set a timer expiry and upon expiration, a new expiry time is set and heartbeat is sent:

         .---------------------------.
         V                           |
    keepalive::start_timer()         |
    {                                |
      timer_.expires_from_now(...);  |
      timer_.async_wait(             |
        [this](...)                  |
        {                            |
          this->start_timer();  -----'
          io.send_headerbeat();
        });
    }
    
    keepalive.start_timer();
    
  • an io class manages the socket, using a private strand to serialize access to the socket. The io class will read from the socket and handle the read data.

        .-------------------------------------.
        V                                     |
    io::read_data()                           |
    {                                         |
      async_read(socket_, buffer_,            |
        strand_.wrap([this](...)              |
        {                                     |
          auto message = parse(buffer_);      |
          this->read_data();  ----------------'
          if (is_heartbeat_request(message))
          {
            this->send_heartbeat();
          }
        }));
    }
    
    io::start()
    {
      strand_.post([this]{ this->read_data(); });
    }
    

In keepalive's call chain, io::send_heartbeat() is invoked from outside of io's private strand to which keepalive has no access. On the other hand, in io's call chain, io::send_heartbeat() is invoked from inside the strand. If io::send_heartbeat() dispatches a handler that will initiate a write operation on the socket, then it will work properly and transparently with both asynchronous call chains. Furthermore, when invoked within the io call chain, the handler is executed immediately, avoiding the overhead of posting and synchronizing with the strand.

io::send_heartbeat()
{
  strand_.dispatch(
    [this]()
    {
      async_write(this->socket_, ...,
        this->strand_.wrap(...));
    });
}

strand::dispatch() will immediately execute the handler in the current thread if either:

  • strand::running_in_this_thread() returns true
  • strand::running_in_this_thread() returns false and the caller is running the io_service and the order of handler invocation specified by strand can be met

There are no public APIs to determine all of the conditions for the second case. Hence, one cannot determine if calling the handler directly is safe, and must instead use to strand::dispatch().

Upvotes: 4

Related Questions