pub struct ThreadpoolTimer { /* private fields */ }Expand description
An owned one-shot thread-pool timer.
Each arming produces exactly one firing. Arm it with
ThreadpoolTimer::set_after or ThreadpoolTimer::set_at, and stop it
with ThreadpoolTimer::disarm. Arming again replaces the previous setting
rather than adding to it.
For repetition, either re-arm from inside the callback with
TimerFiring::rearm_after – which keeps firings strictly sequential – or
use ThreadpoolPeriodicTimer when a fixed cadence matters more than
avoiding overlap.
§When firings can overlap
Re-arming through TimerFiring never overlaps: the request is applied
after the callback returns, so the next firing cannot begin until this one
has finished. That is the intended way to repeat.
Arming from outside the callback is a different matter. Calling
ThreadpoolTimer::set_after while a callback is running can queue the next
firing before the current one returns, and the two then run concurrently on
different pool threads. The callback is Fn + Sync, so this is permitted
rather than unsound – but it means a callback that assumes it is the only
one running must not be driven that way. Re-arm from the callback, or use
ThreadpoolTimer::disarm and ThreadpoolTimer::wait before re-arming
externally.
Drop disarms before draining callbacks, so the captured closure stays
valid for the full lifetime of every callback execution.
§Examples
Fire once:
use std::sync::mpsc;
use std::time::Duration;
use windows_threadpool_sys::timer::ThreadpoolTimer;
let (tx, rx) = mpsc::channel();
let sender = std::sync::Mutex::new(tx);
let timer = ThreadpoolTimer::new(move |_firing| {
let _ = sender.lock().expect("send").send(());
}, None)?;
timer.set_after(Duration::from_millis(10));
rx.recv().expect("the timer should fire");Repeat without ever overlapping, by re-arming from inside the callback. The gap is measured from the end of each firing, so a slow callback delays the next one instead of racing it:
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use windows_threadpool_sys::timer::ThreadpoolTimer;
let ticks = Arc::new(AtomicUsize::new(0));
let counter = Arc::clone(&ticks);
let timer = ThreadpoolTimer::new(move |firing| {
// Stop after three firings by simply not re-arming.
if counter.fetch_add(1, Ordering::SeqCst) < 2 {
firing.rearm_after(Duration::from_millis(1));
}
}, None)?;
timer.set_after(Duration::from_millis(1));
while ticks.load(Ordering::SeqCst) < 3 {
std::thread::yield_now();
}
timer.disarm();
timer.wait();
assert_eq!(ticks.load(Ordering::SeqCst), 3);Implementations§
Source§impl ThreadpoolTimer
impl ThreadpoolTimer
Sourcepub fn new<F>(
callback: F,
env: Option<&mut CallbackEnviron<'_>>,
) -> Result<Self>
pub fn new<F>( callback: F, env: Option<&mut CallbackEnviron<'_>>, ) -> Result<Self>
Create an idle timer that invokes callback each time it expires.
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. It 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 the error from CreateThreadpoolTimer.
Sourcepub fn set_after(&self, delay: Duration)
pub fn set_after(&self, delay: Duration)
Fire once, delay from now.
The delay counts only time the system is awake. A zero delay makes the timer due immediately.
Sourcepub fn set_at(&self, when: SystemTime)
pub fn set_at(&self, when: SystemTime)
Fire once at the wall-clock instant when.
Unlike a relative due time, an absolute one passes through sleep and
hibernation: if when elapses while the machine is asleep, the timer
fires promptly on resume. An instant already in the past fires
immediately.
Sourcepub fn set_after_with_window(&self, delay: Duration, window: Duration)
pub fn set_after_with_window(&self, delay: Duration, window: Duration)
Fire once after delay, allowing the system a coalescing window.
window is the tolerance the system may add to the due time so it can
group this timer with other expirations and wake the processor less
often. A larger window trades timing precision for power.
Sourcepub fn disarm(&self)
pub fn disarm(&self)
Stop the timer.
New callbacks stop being queued, but a callback already queued still
runs; use ThreadpoolTimer::cancel_pending to drop those as well. Disarming an
idle timer is a no-op.
Sourcepub fn is_set(&self) -> bool
pub fn is_set(&self) -> bool
Whether the timer currently has a due time.
This reports whether the timer has been armed and not since disarmed. It
is not a prediction that the timer will fire again: expiring does not
clear the due time, so a fired timer still reports true. Only
ThreadpoolTimer::disarm makes it false.
Sourcepub fn wait(&self)
pub fn wait(&self)
Let every queued callback run, and block until none is executing.
This does not leave a self-re-arming timer idle. A callback’s
TimerFiring::rearm_after is applied after the callback returns, so a
firing that runs during this call installs a fresh due time and the timer
is armed again when it returns. Use
stop_and_drain to reach quiescence.
Sourcepub fn cancel_pending(&self)
pub fn cancel_pending(&self)
Drop callbacks that have not started, then wait for any executing one.
Like wait, this does not by itself leave a self-re-arming
timer idle: it does not suppress the deferred re-arm of a callback that
is already running. Use stop_and_drain when the
timer must actually be quiescent afterwards.
Sourcepub fn stop_and_drain(&self)
pub fn stop_and_drain(&self)
Stop the timer and block until it is idle, leaving it reusable.
This exists because neither disarm nor
cancel_pending can stop a self-re-arming timer
on its own: a callback already running requests its re-arm through
TimerFiring::rearm_after, and the trampoline applies it after the
callback returns – which is after any disarm from outside. This
suppresses that deferred re-arm for the duration of the call, using the
same mechanism Drop uses, and lifts the suppression before returning so
the timer can be armed again afterwards.
§What this guarantees
On return, provided no other thread arms the timer during the call:
- no callback is queued or executing, and
- the timer has no due time – a re-arm requested by a callback that ran during the call is discarded rather than deferred.
§What it does not
A concurrent arm from another thread is not excluded. ThreadpoolTimer
is Sync and set_after, set_at and
set_after_with_window all take &self,
so they do not pass through the suppression this uses. Nothing in this
crate orders such a call against this one.
In practice the drain currently cancels a due time installed that way –
WaitForThreadpoolTimerCallbacks with cancellation clears one even when
no callback is queued, measurably so. That is not a documented contract
and is not relied upon here: if a caller needs the timer to be provably
idle, it must ensure nothing else arms it for the duration, by owning it
exclusively or serializing access to it.
Calling this from inside the timer’s own callback would deadlock, because it waits for that callback to finish.