Структура LocalWaker

Описание

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

LocalWaker аналогичен Waker, но не реализует Send или Sync.

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

Локальные waker могут быть запрошены из Context с помощью метода local_waker.

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

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

Реализует Clone, но не Send или Sync; следовательно, локальный waker не может быть перемещен в другие потоки. В общем случае, при выборе между waker и локальными waker, локальные waker предпочтительнее, если только waker не нужно отправлять между потоками. Это связано с тем, что waker могут нести дополнительные затраты, связанные с синхронизацией памяти.

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

Синтаксис

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

Методы

wake ⚡️

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

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

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

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

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

wake_by_ref ⚡️

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

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

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

will_wake ⚡️

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

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

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

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

new ⚡️ ⚠️

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

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

Указатель данных может использоваться для хранения произвольных данных, требуемых исполнителем. Это может быть, например, type-erased указатель на Arc, связанный с задачей. Значение этого указателя будет передаваться всем функциям, которые являются частью vtable, в качестве первого параметра.

Vtable настраивает поведение LocalWaker. Для каждой операции на LocalWaker будет вызываться связанная функция в vtable.

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

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

from_raw ⚡️ ⚠️

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

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

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

noop ⚡️

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

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

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

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

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

Примеры

#![allow(unused)]
#![feature(local_waker)]
fn main() {
use std::future::Future;
use std::task::{ContextBuilder, LocalWaker, Waker, Poll};

let mut cx = ContextBuilder::from_waker(Waker::noop())
    .local_waker(LocalWaker::noop())
    .build();

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

data ⚡️

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

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

vtable ⚡️

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

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

from_fn_ptr ⚡️

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

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

Примеры

Использование локального waker для реализации future, аналогичного std::thread::yield_now().

#![allow(unused)]
#![feature(local_waker)]
fn main() {
use std::future::{Future, poll_fn};
use std::task::Poll;

// future, который возвращает Pending один раз
fn yield_now() -> impl Future<Output=()> + Unpin {
    let mut yielded = false;
    poll_fn(move |cx| {
        if !yielded {
            yielded = true;
            cx.local_waker().wake_by_ref();
            return Poll::Pending;
        }
        return Poll::Ready(())
    })
}

yield_now().await;
}

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

From<Rc<W>> для LocalWaker

#![allow(unused)]
fn main() {
impl<W> From<Rc<W>> for LocalWaker
where
    W: LocalWake + 'static,
}

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

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

  • Не требует выделения памяти или атомарных операций
  • Работает с типами, не реализующими Send + Sync

Clone

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

clone_from

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

Выполняет копирующее присваивание из source.

Debug

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

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

Drop

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

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

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

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

Назначение

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

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

Преимущества

  • Меньшие накладные расходы по сравнению с Waker
  • Не требует атомарных операций для подсчета ссылок
  • Подходит для однопоточных исполнителей