Asynchronous rate limiter

Non-blocking loop frequency limiter for asyncio.

Note that there is a difference between a (non-blocking) rate limiter and a (blocking) synchronous clock, which lies in the behavior when skipping cycles. A rate limiter does nothing if there is no time left, as the caller’s rate does not need to be limited. On the contrary, a synchronous clock waits for the next tick, which is by definition in the future, so it always waits for a non-zero duration.

class loop_rate_limiters.async_rate_limiter.AsyncRateLimiter(frequency, name='rate limiter', warn=True)

Loop frequency regulator.

Calls to sleep() are non-blocking most of the time but become blocking close to the next clock tick to get more reliable loop frequencies.

This rate limiter is in essence the same as in the one from pymanoid. It relies on the event loop time never jumping backwards nor forwards, so that it does not handle such cases contrary to e.g. rospy.Rate.

name

Human-readable name used for logging.

warn

If set (default), warn when the time between two calls exceeded the rate clock.

property dt: float

Desired period between two calls to sleep(), in seconds.

property measured_period: float

Period measured at the end of the last call to sleep().

This duration is in seconds.

property next_tick: float

Time of next clock tick.

property period: float

Desired period between two calls to sleep(), in seconds.

async remaining()

Get the time remaining until the next expected clock tick.

Return type:

float

Returns:

Time remaining, in seconds, until the next expected clock tick.

property slack: float

Slack duration computed at the last call to sleep().

This duration is in seconds.

async sleep(block_duration=0.0005)

Sleep the duration required to regulate the loop frequency.

This function is meant to be called once per loop cycle.

Parameters:

block_duration (float) – the coroutine blocks the event loop for this duration (in seconds) before the next tick. It is non-blocking before that.

Note

A call to this function will be non-blocking except for the last block_duration seconds of the limiter period.

The block duration helps trim period overshoots and brings the measured period much closer to the desired one (< 2% average error vs. 8-12% average error with a single asyncio.sleep). Empirically a block duration of 0.5 ms gives good behavior at 400 Hz or lower.