In addition to determining the mechanism by which an asynchronous operation delivers its result, a completion token also determines the time when the operation commences. For example, when the completion token is a simple callback the operation commences before the initiating function returns. However, if the completion token's delivery mechanism uses a future, we might instead want to defer initi
| 49 | // To enable this, when implementing an asynchronous operation we must package |
| 50 | // the initiation step as a function object. |
| 51 | struct async_write_message_initiation |
| 52 | { |
| 53 | // The initiation function object's call operator is passed the concrete |
| 54 | // completion handler produced by the completion token. This completion |
| 55 | // handler matches the asynchronous operation's completion handler signature, |
| 56 | // which in this example is: |
| 57 | // |
| 58 | // void(boost::system::error_code error) |
| 59 | // |
| 60 | // The initiation function object also receives any additional arguments |
| 61 | // required to start the operation. (Note: We could have instead passed these |
| 62 | // arguments as members in the initiaton function object. However, we should |
| 63 | // prefer to propagate them as function call arguments as this allows the |
| 64 | // completion token to optimise how they are passed. For example, a lazy |
| 65 | // future which defers initiation would need to make a decay-copy of the |
| 66 | // arguments, but when using a simple callback the arguments can be trivially |
| 67 | // forwarded straight through.) |
| 68 | template <typename CompletionHandler> |
| 69 | void operator()(CompletionHandler&& completion_handler, tcp::socket& socket, |
| 70 | std::unique_ptr<std::string> encoded_message, std::size_t repeat_count, |
| 71 | std::unique_ptr<boost::asio::steady_timer> delay_timer) const |
| 72 | { |
| 73 | // In this example, the composed operation's intermediate completion |
| 74 | // handler is implemented as a hand-crafted function object. |
| 75 | struct intermediate_completion_handler |
| 76 | { |
| 77 | // The intermediate completion handler holds a reference to the socket as |
| 78 | // it is used for multiple async_write operations, as well as for |
| 79 | // obtaining the I/O executor (see get_executor below). |
| 80 | tcp::socket& socket_; |
| 81 | |
| 82 | // The allocated buffer for the encoded message. The std::unique_ptr |
| 83 | // smart pointer is move-only, and as a consequence our intermediate |
| 84 | // completion handler is also move-only. |
| 85 | std::unique_ptr<std::string> encoded_message_; |
| 86 | |
| 87 | // The repeat count remaining. |
| 88 | std::size_t repeat_count_; |
| 89 | |
| 90 | // A steady timer used for introducing a delay. |
| 91 | std::unique_ptr<boost::asio::steady_timer> delay_timer_; |
| 92 | |
| 93 | // To manage the cycle between the multiple underlying asynchronous |
| 94 | // operations, our intermediate completion handler is implemented as a |
| 95 | // state machine. |
| 96 | enum { starting, waiting, writing } state_; |
| 97 | |
| 98 | // As our composed operation performs multiple underlying I/O operations, |
| 99 | // we should maintain a work object against the I/O executor. This tells |
| 100 | // the I/O executor that there is still more work to come in the future. |
| 101 | boost::asio::executor_work_guard<tcp::socket::executor_type> io_work_; |
| 102 | |
| 103 | // The user-supplied completion handler, called once only on completion |
| 104 | // of the entire composed operation. |
| 105 | typename std::decay<CompletionHandler>::type handler_; |
| 106 | |
| 107 | // By having a default value for the second argument, this function call |
| 108 | // operator matches the completion signature of both the async_write and |
no outgoing calls
no test coverage detected