Cancelled operations will immediately post their handlers for deferred invocation.  However, the io_service makes no guarantees on the invocation order of handlers.  Thus, the io_service could choose to invoke the ReadHandlers in either order.  Currently, only a strand specifies guaranteed ordering under certain conditions.
Within a completion handler, if the goal is to know which I/O object was associated with the operation, then consider constructing the completion handler so that it has an explicit handle to the I/O object.  This is often accomplished using any of the following:
- a custom functor
 
std::bind() or boost::bind() 
- a C++11 lambda
 
One common idiom is to have the I/O object be managed by a single class that inherits from boost::enable_shared_from_this<>.  When a class inherits from boost::enable_shared_from_this, it provides a shared_from_this() member function that returns a valid shared_ptr instance to this.   A copy of the shared_ptr is passed to completion handlers, such as a capture-list in lambdas or passed as the instance handle to boost::bind().  This allows for the handlers to know the I/O object on which the operation was performed, and causes the lifetime of the I/O object to be extended to at least as long as the handler.  See the Boost.Asio asynchronous TCP daytime server tutorial for an example using this approach.
class tcp_connection
  : public boost::enable_shared_from_this<tcp_connection>
{
public:
  // ...
  void start()
  {    
    boost::asio::async_write(socket_, ...,
        boost::bind(&tcp_connection::handle_write, shared_from_this(),
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
  }
  void handle_write(
    const boost::system::error_code& error,
    std::size_t bytes_transferred)
  {
    // I/O object is this->socket_.
  }
  tcp::socket socket_;
};
On the other hand, if the goal is to determine if one handler has executed before the other, then:
- the application will need to explicitly manage the state
 
- trying to manage multiple dependent call chains may be introducing unnecessary complexity and often indicates a need to reexamine the design
 
- custom handlers could be used to prioritize the order in which handlers are executed.  The Boost.Asio Invocation example uses custom handlers that are added to a priority queue, which are then executed at a later point in time.