pub struct ThreadpoolPeriodicTimer { /* private fields */ }Expand description
An owned repeating thread-pool timer.
The period is fixed when the timer is created, so the type says what it is: this object exists to tick on a cadence, and there is no argument that can quietly turn it into a one-shot.
§Ticks can overlap
The pool queues each tick on schedule regardless of whether the previous tick has finished. If the callback takes longer than the period, two or more runs of it will execute concurrently on different pool threads. This is the property that makes periodic timers surprising in practice, and it follows from the cadence being fixed: the schedule cannot wait for the callback without ceasing to be a schedule.
So a ThreadpoolPeriodicTimer callback must be safe to run concurrently with itself.
If that is awkward, the alternative is a one-shot ThreadpoolTimer re-armed from
inside its own callback with crate::timer::TimerFiring::rearm_after:
there is never more than one firing outstanding, and the gap is measured from
the end of each firing rather than from a fixed schedule.
ThreadpoolPeriodicTimer | ThreadpoolTimer + rearm_after | |
|---|---|---|
| Cadence | fixed, independent of callback duration | drifts by the callback duration |
| Concurrent runs of the callback | possible | never |
| Slow callback | ticks pile up and overlap | next tick simply happens later |
§Teardown
Drop stops the timer before draining callbacks, so it cannot requeue
during teardown. ThreadpoolPeriodicTimer::stop_and_drain does the same thing under
the caller’s control, and is the ordering to copy if doing it by hand:
stop first, drain second.
§Examples
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use windows_threadpool_sys::timer::ThreadpoolPeriodicTimer;
let ticks = Arc::new(AtomicUsize::new(0));
let counter = Arc::clone(&ticks);
// The period belongs to the timer, not to a call.
let timer = ThreadpoolPeriodicTimer::new(Duration::from_millis(1), move |_tick| {
counter.fetch_add(1, Ordering::SeqCst);
}, None)?;
timer.start();
while ticks.load(Ordering::SeqCst) < 3 {
std::thread::yield_now();
}
timer.stop_and_drain();
assert!(ticks.load(Ordering::SeqCst) >= 3);Stopping from inside the callback, for “tick until done”. Note the counter may pass the threshold, because a tick already queued still runs:
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use windows_threadpool_sys::timer::ThreadpoolPeriodicTimer;
let ticks = Arc::new(AtomicUsize::new(0));
let counter = Arc::clone(&ticks);
let timer = ThreadpoolPeriodicTimer::new(Duration::from_millis(1), move |tick| {
if counter.fetch_add(1, Ordering::SeqCst) >= 2 {
tick.stop();
}
}, None)?;
timer.start();
while timer.is_running() {
std::thread::yield_now();
}
timer.stop_and_drain();
assert!(ticks.load(Ordering::SeqCst) >= 3);Implementations§
Source§impl ThreadpoolPeriodicTimer
impl ThreadpoolPeriodicTimer
Sourcepub const MIN_PERIOD: Duration
pub const MIN_PERIOD: Duration
The shortest period this timer can express: one millisecond.
SetThreadpoolTimer takes the period as whole milliseconds, so nothing
shorter can be represented. This is a floor on what may be asked for,
not a promise about delivery: ticks arrive on the system timer tick,
which is far coarser (~15.6ms by default).
Sourcepub const MAX_PERIOD: Duration
pub const MAX_PERIOD: Duration
The longest period this timer can express: u32::MAX milliseconds, or
just under 50 days.
The pool’s period field is a u32 count of milliseconds, so a longer
period cannot be represented.
Sourcepub fn new<F>(
period: Duration,
callback: F,
env: Option<&mut CallbackEnviron<'_>>,
) -> Result<Self>
pub fn new<F>( period: Duration, callback: F, env: Option<&mut CallbackEnviron<'_>>, ) -> Result<Self>
Create a stopped timer that invokes callback every period.
period must be a whole number of milliseconds between
MIN_PERIOD and MAX_PERIOD;
anything else is rejected. The pool takes the period as a u32 count of
milliseconds, so every other value would be quietly altered to fit:
- a period below a millisecond rounds down to zero, and a zero period
means “do not repeat” – the timer would fire once and stop, which is a
ThreadpoolPeriodicTimersilently behaving like aThreadpoolTimer; - a fractional period such as 1.5ms is truncated, so the timer would tick
at 1ms while
periodstill reported 1.5ms; - a period beyond
u32::MAXmilliseconds would be capped, ticking far more often than asked.
Rejecting rather than rounding keeps period an accurate
report of what was scheduled.
Pass Some(env) to select a private pool or callback priority; None
uses the process-default pool with default priority.
The callback runs on a shared, process-managed pool thread, may run
concurrently with itself (see the type documentation), must restore any
thread state it changes, and must not terminate its thread. It must not
panic: a panic unwinds to the extern "system" trampoline and aborts the
process.
§Errors
Returns io::ErrorKind::InvalidInput if period is outside
MIN_PERIOD..=MAX_PERIOD or is
not a whole number of milliseconds, or the error from
CreateThreadpoolTimer.
Sourcepub fn start_after(&self, first_delay: Duration)
pub fn start_after(&self, first_delay: Duration)
Start ticking, with the first tick first_delay from now.
Subsequent ticks follow every ThreadpoolPeriodicTimer::period. A zero
first_delay makes the first tick due immediately.
Sourcepub fn start_at(&self, when: SystemTime)
pub fn start_at(&self, when: SystemTime)
Start ticking, with the first tick at the wall-clock instant when.
Unlike a relative first delay, an absolute one passes through sleep and hibernation.
Sourcepub fn start_with_window(&self, first_delay: Duration, window: Duration)
pub fn start_with_window(&self, first_delay: Duration, window: Duration)
Start ticking, allowing the system a coalescing window on each tick.
window is the tolerance the system may add so it can group this timer
with other expirations and wake the processor less often, trading timing
precision for power.
Sourcepub fn stop(&self)
pub fn stop(&self)
Stop the timer.
Future ticks stop being queued, but a tick already queued still runs and
ticks already executing are unaffected. Use
ThreadpoolPeriodicTimer::stop_and_drain to also wait for those.
Sourcepub fn is_running(&self) -> bool
pub fn is_running(&self) -> bool
Whether the timer is currently started.
Ticking does not clear the schedule, so this stays true until something
stops the timer – ThreadpoolPeriodicTimer::stop, PeriodicTick::stop, or
teardown.
Sourcepub fn wait(&self)
pub fn wait(&self)
Block until all queued and executing ticks have completed.
Stop the timer first, or this waits for a schedule that keeps producing
new ticks. ThreadpoolPeriodicTimer::stop_and_drain does both in the right order.
Sourcepub fn stop_and_drain(&self)
pub fn stop_and_drain(&self)
Stop the timer and wait until no tick is queued or executing.
This is the correct teardown order – stop first, drain second – and is
what Drop performs. Ticks that have not started are dropped rather
than run.
The result holds provided no other thread starts the timer during the
call. ThreadpoolPeriodicTimer is Sync and the start* methods take
&self, so a start landing between the stop and the drain is not
excluded by anything here, and would leave a schedule installed on
return. A caller needing the timer to be provably stopped must own it
exclusively or serialize access to it. Unlike the one-shot timer there is
no re-arm to suppress: PeriodicTick::stop only ever stops.