// // Copyright (c) 2003-2023 Christopher M. Kohlhoff (chris at kohlhoff dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // /** \page tuttimer1 Timer.1 - Using a timer synchronously This tutorial program introduces asio by showing how to perform a blocking wait on a timer. \dontinclude timer1/timer.cpp \skip #include We start by including the necessary header files. All of the asio classes can be used by simply including the "asio.hpp" header file. \until asio.hpp All programs that use asio need to have at least one I/O execution context, such as an asio::io_context or asio::thread_pool object. An I/O execution context provides access to I/O functionality. We declare an object of type asio::io_context first thing in the main function. \until asio::io_context Next we declare an object of type asio::steady_timer. The core asio classes that provide I/O functionality (or as in this case timer functionality) always take an executor, or a reference to an execution context (such as asio::io_context), as their first constructor argument. The second argument to the constructor sets the timer to expire 5 seconds from now. \until asio::steady_timer In this simple example we perform a blocking wait on the timer. That is, the call to asio::steady_timer::wait() will not return until the timer has expired, 5 seconds after it was created (i.e. not from when the wait starts). A timer is always in one of two states: "expired" or "not expired". If the asio::steady_timer::wait() function is called on an expired timer, it will return immediately. \until wait Finally we print the obligatory "Hello, world!" message to show when the timer has expired. \until } See the \ref tuttimer1src "full source listing" \n Return to the \ref index "tutorial index" \n Next: \ref tuttimer2 */ /** \page tuttimer1src Source listing for Timer.1 \include timer1/timer.cpp Return to \ref tuttimer1 */ /** \page tuttimer2 Timer.2 - Using a timer asynchronously This tutorial program demonstrates how to use asio's asynchronous functionality by modifying the program from tutorial Timer.1 to perform an asynchronous wait on the timer. \dontinclude timer2/timer.cpp \skip #include \until asio.hpp Using asio's asynchronous functionality means supplying a @ref completion_token, which determines how the result will be delivered to a completion handler when an @ref asynchronous_operation completes. In this program we define a function called print to be called when the asynchronous wait finishes. \until asio::steady_timer Next, instead of doing a blocking wait as in tutorial Timer.1, we call the asio::steady_timer::async_wait() function to perform an asynchronous wait. When calling this function we pass the print function that was defined above. \skipline async_wait Finally, we must call the asio::io_context::run() member function on the io_context object. The asio library provides a guarantee that completion handlers will only be called from threads that are currently calling asio::io_context::run(). Therefore unless the asio::io_context::run() function is called the completion handler for the asynchronous wait completion will never be invoked. The asio::io_context::run() function will also continue to run while there is still "work" to do. In this example, the work is the asynchronous wait on the timer, so the call will not return until the timer has expired and the completion handler has returned. It is important to remember to give the io_context some work to do before calling asio::io_context::run(). For example, if we had omitted the above call to asio::steady_timer::async_wait(), the io_context would not have had any work to do, and consequently asio::io_context::run() would have returned immediately. \skip run \until } See the \ref tuttimer2src "full source listing" \n Return to the \ref index "tutorial index" \n Previous: \ref tuttimer1 \n Next: \ref tuttimer3 */ /** \page tuttimer2src Source listing for Timer.2 \include timer2/timer.cpp Return to \ref tuttimer2 */ /** \page tuttimer3 Timer.3 - Binding arguments to a completion handler In this tutorial we will modify the program from tutorial Timer.2 so that the timer fires once a second. This will show how to pass additional parameters to your handler function. \dontinclude timer3/timer.cpp \skip #include \until bind.hpp To implement a repeating timer using asio you need to change the timer's expiry time in your completion handler, and to then start a new asynchronous wait. Obviously this means that the completion handler will need to be able to access the timer object. To this end we add two new parameters to the print function: \li a pointer to a timer object; and \li a counter so that we can stop the program when the timer fires for the sixth time at the end of the parameter list. \until { As mentioned above, this tutorial program uses a counter to stop running when the timer fires for the sixth time. However you will observe that there is no explicit call to ask the io_context to stop. Recall that in tutorial Timer.2 we learnt that the asio::io_context::run() function completes when there is no more "work" to do. By not starting a new asynchronous wait on the timer when count reaches 5, the io_context will run out of work and stop running. \until ++ Next we move the expiry time for the timer along by one second from the previous expiry time. By calculating the new expiry time relative to the old, we can ensure that the timer does not drift away from the whole-second mark due to any delays in processing the handler. \until expires_at Then we start a new asynchronous wait on the timer. As you can see, the \ref boost_bind function is used to associate the extra parameters with your completion handler. The asio::steady_timer::async_wait() function expects a handler function (or function object) with the signature void(const asio::error_code&). Binding the additional parameters converts your print function into a function object that matches the signature correctly. See the Boost.Bind documentation for more information on how to use \ref boost_bind. In this example, the asio::placeholders::error argument to \ref boost_bind is a named placeholder for the error object passed to the handler. When initiating the asynchronous operation, and if using \ref boost_bind, you must specify only the arguments that match the handler's parameter list. In tutorial Timer.4 you will see that this placeholder may be elided if the parameter is not needed by the completion handler. \until asio::io_context A new count variable is added so that we can stop the program when the timer fires for the sixth time. \until asio::steady_timer As in Step 4, when making the call to asio::steady_timer::async_wait() from main we bind the additional parameters needed for the print function. \until run Finally, just to prove that the count variable was being used in the print handler function, we will print out its new value. \until } See the \ref tuttimer3src "full source listing" \n Return to the \ref index "tutorial index" \n Previous: \ref tuttimer2 \n Next: \ref tuttimer4 */ /** \page tuttimer3src Source listing for Timer.3 \include timer3/timer.cpp Return to \ref tuttimer3 */ /** \page tuttimer4 Timer.4 - Using a member function as a completion handler In this tutorial we will see how to use a class member function as a completion handler. The program should execute identically to the tutorial program from tutorial Timer.3. \dontinclude timer4/timer.cpp \skip #include \until bind.hpp Instead of defining a free function print as the completion handler, as we did in the earlier tutorial programs, we now define a class called printer. \until public The constructor of this class will take a reference to the io_context object and use it when initialising the timer_ member. The counter used to shut down the program is now also a member of the class. \until { The \ref boost_bind function works just as well with class member functions as with free functions. Since all non-static class member functions have an implicit this parameter, we need to bind this to the function. As in tutorial Timer.3, \ref boost_bind converts our completion handler (now a member function) into a function object that can be invoked as though it has the signature void(const asio::error_code&). You will note that the asio::placeholders::error placeholder is not specified here, as the print member function does not accept an error object as a parameter. \until } In the class destructor we will print out the final value of the counter. \until } The print member function is very similar to the print function from tutorial Timer.3, except that it now operates on the class data members instead of having the timer and counter passed in as parameters. \until }; The main function is much simpler than before, as it now declares a local printer object before running the io_context as normal. \until } See the \ref tuttimer4src "full source listing" \n Return to the \ref index "tutorial index" \n Previous: \ref tuttimer3 \n Next: \ref tuttimer5 \n */ /** \page tuttimer4src Source listing for Timer.4 \include timer4/timer.cpp Return to \ref tuttimer4 */ /** \page tuttimer5 Timer.5 - Synchronising completion handlers in multithreaded programs This tutorial demonstrates the use of the asio::strand class template to synchronise completion handlers in a multithreaded program. The previous four tutorials avoided the issue of handler synchronisation by calling the asio::io_context::run() function from one thread only. As you already know, the asio library provides a guarantee that completion handlers will only be called from threads that are currently calling asio::io_context::run(). Consequently, calling asio::io_context::run() from only one thread ensures that completion handlers cannot run concurrently. The single threaded approach is usually the best place to start when developing applications using asio. The downside is the limitations it places on programs, particularly servers, including: If you find yourself running into these limitations, an alternative approach is to have a pool of threads calling asio::io_context::run(). However, as this allows handlers to execute concurrently, we need a method of synchronisation when handlers might be accessing a shared, thread-unsafe resource. \dontinclude timer5/timer.cpp \skip #include \until bind.hpp We start by defining a class called printer, similar to the class in the previous tutorial. This class will extend the previous tutorial by running two timers in parallel. \until public In addition to initialising a pair of asio::steady_timer members, the constructor initialises the strand_ member, an object of type asio::strand. The asio::strand class template is an executor adapter that guarantees that, for those handlers that are dispatched through it, an executing handler will be allowed to complete before the next one is started. This is guaranteed irrespective of the number of threads that are calling asio::io_context::run(). Of course, the handlers may still execute concurrently with other handlers that were not dispatched through an asio::strand, or were dispatched through a different asio::strand object. \until { When initiating the asynchronous operations, each completion handler is "bound" to an asio::strand object. The asio::bind_executor() function returns a new handler that automatically dispatches its contained handler through the asio::strand object. By binding the handlers to the same asio::strand, we are ensuring that they cannot execute concurrently. \until } \until } In a multithreaded program, the handlers for asynchronous operations should be synchronised if they access shared resources. In this tutorial, the shared resources used by the handlers (print1 and print2) are std::cout and the count_ data member. \until }; The main function now causes asio::io_context::run() to be called from two threads: the main thread and one additional thread. This is accomplished using an asio::thread object. Just as it would with a call from a single thread, concurrent calls to asio::io_context::run() will continue to execute while there is "work" left to do. The background thread will not exit until all asynchronous operations have completed. \until } See the \ref tuttimer5src "full source listing" \n Return to the \ref index "tutorial index" \n Previous: \ref tuttimer4 \n */ /** \page tuttimer5src Source listing for Timer.5 \include timer5/timer.cpp Return to \ref tuttimer5 */