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 in include/asio/steady_timer.hpp): Uses std::chrono::steady_clock for monotonic time measurement. This is the recommended choice for new code because it is immune to system clock adjustments.
  • deadline_timer (defined in include/asio/deadline_timer.hpp): Deprecated typedef using boost::posix_time::ptime from Boost.Date_Time. Useful for legacy integrations but lacks the monotonic guarantees of steady_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_timer for new development in include/asio/steady_timer.hpp to leverage C++11 chrono and monotonic clock guarantees.
  • Use deadline_timer only when maintaining legacy code requiring Boost.Date_Time integration.
  • Modify expiry via expires_after() or expires_at() to cancel pending operations; these methods return the count of cancelled handlers.
  • Check for asio::error::operation_aborted in 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →