Структура Waker

Описание

Waker - это дескриптор для пробуждения задачи, уведомляющий её исполнитель о готовности к выполнению.

Этот дескриптор инкапсулирует экземпляр RawWaker, который определяет специфичное для исполнителя поведение пробуждения.

Типичный жизненный цикл Waker:

  1. Создается исполнителем
  2. Оборачивается в Context
  3. Передается в Future::poll()
  4. Если future возвращает Poll::Pending, он должен сохранить waker и вызвать Waker::wake(), когда future нужно опросить снова

Реализует Clone, Send и Sync; следовательно, waker может быть вызван из любого потока, включая те, которые никак не управляются исполнителем. Например, это может быть сделано для пробуждения future, когда блокирующий вызов функции завершается в другом потоке.

Примечание: предпочтительнее использовать waker.clone_from(&new_waker) вместо *waker = new_waker.clone(), так как первый вариант избежит ненужного клонирования waker, если оба waker пробуждают одну и ту же задачу.

Создание Waker из RawWaker небезопасно. Реализация трейта Wake является безопасной альтернативой, но требует выделения памяти.

Синтаксис

#![allow(unused)]
fn main() {
pub struct Waker { /* приватные поля */ }
}

Методы

wake

#![allow(unused)]
fn main() {
pub fn wake(self)
}

Пробуждает задачу, связанную с этим Waker.

Пока исполнитель продолжает работать и задача не завершена, гарантируется, что каждый вызов wake() (или wake_by_ref()) будет сопровождаться как минимум одним вызовом poll() задачи, к которой принадлежит этот Waker. Это позволяет временно уступать другим задачам во время выполнения потенциально неограниченных циклов обработки.

Примечание: вышесказанное подразумевает, что множественные пробуждения могут быть объединены в один вызов poll() средой выполнения.

Также учтите, что уступка конкурирующим задачам не гарантируется: исполнитель сам выбирает, какую задачу запускать, и может выбрать повторный запуск текущей задачи.

wake_by_ref

#![allow(unused)]
fn main() {
pub fn wake_by_ref(&self)
}

Пробуждает задачу, связанную с этим Waker, не потребляя Waker.

Это похоже на wake(), но может быть немного менее эффективно в случае, когда доступен owned Waker. Этот метод следует предпочитать вызову waker.clone().wake().

will_wake

#![allow(unused)]
fn main() {
pub fn will_wake(&self, other: &Waker) -> bool
}

Возвращает true, если этот Waker и другой Waker пробуждают одну и ту же задачу.

Эта функция работает по принципу "best-effort" и может возвращать false, даже если Waker пробуждают одну и ту же задачу. Однако, если функция возвращает true, гарантируется, что Waker пробуждают одну и ту же задачу.

Эта функция в основном используется для оптимизации — например, реализация clone_from этого типа использует её, чтобы избежать клонирования waker, когда они и так пробуждают одну и ту же задачу.

new ⚠️

#![allow(unused)]
fn main() {
pub const unsafe fn new(
    data: *const (),
    vtable: &'static RawWakerVTable,
) -> Waker
}

Создает новый Waker из предоставленного указателя данных и vtable.

Доступность: 1.83.0 (const: 1.83.0)

Безопасность

Поведение возвращаемого Waker не определено, если контракт, определенный в документации RawWakerVTable, не соблюдается.

from_raw ⚠️

#![allow(unused)]
fn main() {
pub const unsafe fn from_raw(waker: RawWaker) -> Waker
}

Создает новый Waker из RawWaker.

Доступность: 1.36.0 (const: 1.82.0)

Безопасность

Поведение возвращаемого Waker не определено, если контракт, определенный в документации RawWaker и RawWakerVTable, не соблюдается.

noop

#![allow(unused)]
fn main() {
pub const fn noop() -> &'static Waker
}

Возвращает ссылку на Waker, который ничего не делает при использовании.

Доступность: 1.85.0 (const: 1.85.0)

В основном полезно для написания тестов, которым нужен Context для опроса некоторых future, но которые не ожидают, что эти future пробудят waker, или не нужно делать ничего специфического, если это произойдет.

В более общем смысле, использование Waker::noop() для опроса future означает отбрасывание уведомления о том, когда future следует опросить снова. Поэтому это следует использовать только тогда, когда такое уведомление не потребуется для прогресса.

Если нужен owned Waker, можно клонировать этот.

Примеры

#![allow(unused)]
fn main() {
use std::future::Future;
use std::task;

let mut cx = task::Context::from_waker(task::Waker::noop());

let mut future = Box::pin(async { 10 });
assert_eq!(future.as_mut().poll(&mut cx), task::Poll::Ready(10));
}

data

#![allow(unused)]
fn main() {
pub fn data(&self) -> *const ()
}

Получает указатель данных, использованный для создания этого Waker.

Доступность: 1.83.0

vtable

#![allow(unused)]
fn main() {
pub fn vtable(&self) -> &'static RawWakerVTable
}

Получает указатель vtable, использованный для создания этого Waker.

Доступность: 1.83.0

from_fn_ptr ⚡️

#![allow(unused)]
fn main() {
pub const fn from_fn_ptr(f: fn()) -> Waker
}

Создает Waker из указателя на функцию.

Примечание: Это экспериментальное API, доступно только в ночных сборках Rust.

Реализации трейтов

From<Arc<W>> для Waker

#![allow(unused)]
fn main() {
impl<W> From<Arc<W>> for Waker
where
    W: Wake + Send + Sync + 'static,
}

Использует пробуждаемый тип как Waker.

Особенности:

  • Не требует выделения памяти или атомарных операций
  • Доступно только на платформах с target_has_atomic=ptr

Clone

#![allow(unused)]
fn main() {
impl Clone for Waker
}

clone_from

#![allow(unused)]
fn main() {
fn clone_from(&mut self, source: &Waker)
}

Присваивает клон source для self, если только self.will_wake(source) и так не возвращает true.

Этот метод предпочтительнее простого присваивания source.clone() для self, так как он избегает клонирования waker, если self уже является тем же waker.

Примеры

#![allow(unused)]
fn main() {
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll, Waker};

struct Waiter {
    shared: Arc<Mutex<Shared>>,
}

struct Shared {
    waker: Waker,
    // ...
}

impl Future for Waiter {
    type Output = ();
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
        let mut shared = self.shared.lock().unwrap();

        // обновляем waker
        shared.waker.clone_from(cx.waker());

        // логика готовности ...
    }
}
}

Debug

#![allow(unused)]
fn main() {
impl Debug for Waker
}

Позволяет форматирование Waker для отладки.

Drop

#![allow(unused)]
fn main() {
impl Drop for Waker
}

Реализует деструктор для Waker.

Автоматические реализации трейтов

  • Freeze for Waker - может быть заморожен
  • RefUnwindSafe for Waker - безопасен для паники
  • UnwindSafe for Waker - безопасен для паники
  • Send for Waker - может быть передан между потоками
  • Sync for Waker - может быть разделен между потоками
  • Unpin for Waker - может быть безопасно перемещено

Назначение

Waker используется для:

  • Пробуждения асинхронных задач
  • Интеграции с системными событиями
  • Создания эффективных исполнителей
  • Реализации сложных асинхронных паттернов