pub struct ThreadpoolWait { /* private fields */ }Expand description
An owned thread-pool wait object bound to one waitable handle.
The object owns the handle, so the handle cannot be closed while a wait is
pending. A newly created wait is idle; arm it with ThreadpoolWait::arm,
and rearm from inside the callback with WaitActivation::rearm.
Drop disarms before draining callbacks, then closes the object and only
afterwards releases the callback context and the handle.
Unlike ThreadpoolTimer, the callback can
run concurrently with itself: a wait’s re-arm takes effect immediately, so
re-arming while the handle is still signalled queues the next activation
before the current callback returns. See WaitActivation::rearm for the
measurements and the two ways to avoid it.
§Examples
Watch an event once. The wait takes ownership of the handle, and
ThreadpoolWait::handle borrows it back for signalling:
use std::os::windows::io::AsRawHandle;
use std::sync::mpsc;
use windows_sys::Win32::System::Threading::SetEvent;
use windows_threadpool_sys::wait::{ThreadpoolWait, WaitResult, WaitableHandle};
let event = WaitableHandle::event(true, false)?;
let (tx, rx) = mpsc::channel();
let sender = std::sync::Mutex::new(tx);
let wait = ThreadpoolWait::new(event, move |activation| {
let _ = sender.lock().expect("send").send(activation.result());
}, None)?;
wait.arm(None);
// SAFETY: the wait owns the event, so the handle is still open.
unsafe { SetEvent(wait.handle().as_raw_handle()) };
assert_eq!(rx.recv().expect("activation"), WaitResult::Signalled);Keep watching across activations by rearming from inside the callback, which is what the SDK requires – an activation consumes the arming:
use windows_threadpool_sys::wait::{ThreadpoolWait, WaitableHandle};
let event = WaitableHandle::event(false, false)?;
let seen = Arc::new(AtomicUsize::new(0));
let counter = Arc::clone(&seen);
let wait = ThreadpoolWait::new(event, move |activation| {
counter.fetch_add(1, Ordering::SeqCst);
activation.rearm(None);
}, None)?;
wait.arm(None);
for _ in 0..3 {
// SAFETY: the wait owns the event, so the handle is still open.
unsafe { SetEvent(wait.handle().as_raw_handle()) };
std::thread::sleep(std::time::Duration::from_millis(5));
}
wait.disarm();
wait.wait();
assert!(seen.load(Ordering::SeqCst) >= 1);Implementations§
Source§impl ThreadpoolWait
impl ThreadpoolWait
Sourcepub fn new<F>(
handle: WaitableHandle,
callback: F,
env: Option<&mut CallbackEnviron<'_>>,
) -> Result<Self>
pub fn new<F>( handle: WaitableHandle, callback: F, env: Option<&mut CallbackEnviron<'_>>, ) -> Result<Self>
Create an idle wait watching handle.
The object takes ownership of the handle and closes it on drop, which is what guarantees the handle outlives any pending wait.
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, 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.
Taking a WaitableHandle rather than a bare handle is what keeps this
constructor safe: the thread pool does not support every waitable object,
and a mutex handle in particular is undefined rather than an error.
§Errors
Returns the error from CreateThreadpoolWait.
Sourcepub fn handle(&self) -> BorrowedHandle<'_>
pub fn handle(&self) -> BorrowedHandle<'_>
Borrow the watched handle, for signalling or inspecting it.
Sourcepub fn arm(&self, timeout: Option<Duration>)
pub fn arm(&self, timeout: Option<Duration>)
Arm the wait, so the next signal or timeout runs the callback once.
timeout of None waits indefinitely. Arming replaces any previous
arming rather than adding to it, and an activation consumes the arming –
rearm from inside the callback with WaitActivation::rearm to keep
watching.
Sourcepub fn disarm(&self)
pub fn disarm(&self)
Stop watching.
New activations stop being queued, but a callback already queued still
runs; use ThreadpoolWait::cancel_pending to drop those as well.
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 wait idle: a callback running
during this call can rearm before it returns,
so the object is watching again when this 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
wait idle: it does not suppress the re-arm of a callback that is already
running. Use stop_and_drain when the wait must
actually be quiescent afterwards.
Sourcepub fn stop_and_drain(&self)
pub fn stop_and_drain(&self)
Stop watching and block until the wait is idle, leaving it reusable.
This exists because neither disarm nor
cancel_pending can stop a self-re-arming wait on
its own: a callback already running can call WaitActivation::rearm
after a disarm from outside has taken effect. This suppresses re-arming
for the duration of the call, using the same mechanism Drop uses, and
lifts the suppression before returning so the wait can be armed again.
§What this guarantees
On return, provided no other thread arms the wait during the call:
- no callback is queued or executing, and
- the object is not watching – 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.
ThreadpoolWait is Sync and arm takes &self, so it does not pass
through the suppression this uses, and nothing in this crate orders such
a call against this one. A caller needing the wait to be provably idle
must ensure nothing else arms it for the duration, by owning it
exclusively or serializing access to it.
Calling this from inside the wait’s own callback would deadlock, because it waits for that callback to finish.