How to Use Asio steady_timer and deadline_timer for Timeouts
asio::steady_timer and asio::deadline_timer provide portable asynchronous timeout mechanisms built on the basic_waitable_timer template, with steady_timer preferred for modern C++ chrono support and deadline_timer maintained for legacy Boost.Date_Time compatibility.
The chriskohlhoff/asio repository offers two distinct timer implementations for managing timeouts in asynchronous operations. Understanding how to use asio steady_timer and deadline_timer for timeouts enables developers to implement robust deadline semantics, whether using modern C++11 chrono facilities or maintaining legacy codebases. Both classes share underlying mechanics through basic_waitable_timer but expose different clock abstractions and header interfaces.
Understanding Asio Timer Types
steady_timer vs deadline_timer
The Asio library provides these timers through separate headers with distinct clock semantics:
steady_timer(defined ininclude/asio/steady_timer.hpp): Usesstd::chrono::steady_clockfor monotonic time measurement. This is the recommended choice for new code because it is immune to system clock adjustments.deadline_timer(defined ininclude/asio/deadline_timer.hpp): Deprecated typedef usingboost::posix_time::ptimefrom Boost.Date_Time. Useful for legacy integrations but lacks the monotonic guarantees ofsteady_timer.
Both are specializations of the basic_waitable_timer template found in include/asio/basic_waitable_timer.hpp, which handles the core timer state machine and asynchronous wait operations.
Core Architecture
The timer implementation delegates to asio::detail::deadline_timer_service (located in include/asio/detail/deadline_timer_service.hpp), which maintains a timer queue integrated with the I/O reactor. When you call async_wait(), the service registers the operation; when the expiry time passes, the handler is posted to the timer's associated executor.
Implementing Timeouts with steady_timer
For modern C++ applications, steady_timer provides intuitive chrono-based APIs.
Synchronous Timeout
Use wait() to block the current thread until the timer expires:
#include <asio.hpp>
#include <chrono>
int main() {
asio::io_context io;
asio::steady_timer t(io);
t.expires_after(std::chrono::seconds(2));
t.wait(); // Blocks until expiry
std::cout << "Timeout completed\n";
}
Asynchronous Timeout
Use async_wait() with a completion handler for non-blocking operations:
t.expires_after(std::chrono::seconds(5));
t.async_wait([](const asio::error_code& ec) {
if (!ec) {
std::cout << "Timer expired normally\n";
}
});
The expires_after() method accepts any std::chrono::duration, while expires_at() accepts a std::chrono::time_point for absolute deadlines.
Legacy deadline_timer Usage
When working with existing Boost.Date_Time code, include include/asio/deadline_timer.hpp and use the legacy API:
#include <asio/deadline_timer.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
void handler(const asio::error_code& ec);
int main() {
asio::io_context io;
asio::deadline_timer t(io);
// Relative timeout
t.expires_from_now(boost::posix_time::seconds(3));
// Absolute timeout
t.expires_at(boost::posix_time::second_clock::universal_time()
+ boost::posix_time::seconds(5));
t.async_wait(handler);
io.run();
}
Note that expires_from_now() and expires_at() here use Boost.Date_Time types rather than chrono.
Timer Cancellation Mechanics
Cancelling Pending Waits
Modifying a timer's expiry while an asynchronous wait is outstanding cancels that wait. Both expires_after() and expires_at() return the number of cancelled handlers (0 if too late to cancel, 1 if successful):
// Returns 1 if handler was cancelled, 0 if already running
std::size_t cancelled = t.expires_after(std::chrono::seconds(10));
Cancelled handlers receive asio::error::operation_aborted as the error code, allowing you to distinguish between natural expiry and cancellation.
Implementing a Reset-on-Activity Timeout
To create a watchdog timer that resets when activity occurs, use asio::bind_executor with a strand for thread safety:
#include <asio.hpp>
#include <chrono>
class watchdog {
public:
explicit watchdog(asio::io_context& ctx)
: timer_(ctx), strand_(asio::make_strand(ctx)) {}
void kick() {
asio::post(strand_, [this]() {
// Cancel pending wait and restart timer
if (timer_.expires_after(std::chrono::seconds(5)) > 0) {
timer_.async_wait(
asio::bind_executor(strand_,
&watchdog::expired, this));
}
});
}
private:
void expired(const asio::error_code& ec) {
if (ec != asio::error::operation_aborted) {
std::cout << "Timeout - no activity for 5 seconds\n";
}
}
asio::steady_timer timer_;
asio::strand<asio::io_context::executor_type> strand_;
};
This pattern leverages the fact that expires_after() cancels existing waits before establishing the new deadline.
Composing Multiple Timers
You can chain timers using the expiry() method to retrieve a timer's current deadline:
asio::steady_timer t1(io);
asio::steady_timer t2(io);
t1.expires_after(std::chrono::seconds(2));
t1.async_wait([](auto){ std::cout << "First timer done\n"; });
// t2 expires 3 seconds after t1 expires
t2.expires_at(t1.expiry() + std::chrono::seconds(3));
t2.async_wait([](auto){ std::cout << "Second timer done\n"; });
Summary
- Use
steady_timerfor new development ininclude/asio/steady_timer.hppto leverage C++11 chrono and monotonic clock guarantees. - Use
deadline_timeronly when maintaining legacy code requiring Boost.Date_Time integration. - Modify expiry via
expires_after()orexpires_at()to cancel pending operations; these methods return the count of cancelled handlers. - Check for
asio::error::operation_abortedin completion handlers to handle cancellation cases correctly. - Access deadline information using the
expiry()method to compose complex timer sequences.
Frequently Asked Questions
What is the difference between expires_after and expires_from_now?
expires_after() is the modern method used with steady_timer, accepting std::chrono::duration values like std::chrono::seconds. expires_from_now() is the legacy method for deadline_timer that accepts boost::posix_time::duration objects. Both set relative timeouts, but expires_after is preferred for type safety and standard library compatibility.
How do I cancel an active asynchronous wait on an Asio timer?
Call expires_after(), expires_at(), or cancel() on the timer instance. These methods immediately cancel any pending wait operations and return the number of handlers cancelled (typically 0 or 1). The cancelled handlers will be invoked with asio::error::operation_aborted guaranteed by the basic_waitable_timer implementation in include/asio/basic_waitable_timer.hpp.
Why should I prefer steady_timer over deadline_timer?
steady_timer uses std::chrono::steady_clock, which provides monotonic time (always moves forward) and is immune to system clock adjustments. deadline_timer uses boost::posix_time::ptime based on the system clock, which can jump backward or forward if the system time is changed, potentially causing missed deadlines or premature timeouts.
What error code do cancelled timer handlers receive?
Cancelled handlers receive asio::error::operation_aborted in their asio::error_code parameter. This specific error code indicates that the wait operation was cancelled explicitly via cancel(), expires_after(), or destruction of the timer object, distinguishing it from successful expiry or other system errors.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →