Трейт LocalWake

Описание

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

Аналогичный трейт Wake, но используемый для создания LocalWaker.

Этот API работает точно так же, как Wake, за исключением того, что он использует Rc вместо Arc, и результатом является LocalWaker вместо Waker.

Преимущества использования LocalWaker вместо Waker заключаются в том, что он позволяет локальному waker хранить данные, которые не реализуют Send и Sync. Дополнительно, это экономит вызовы Arc::clone, которые требуют атомарной синхронизации.

Синтаксис

#![allow(unused)]
fn main() {
pub trait LocalWake {
    // Обязательный метод
    fn wake(self: Rc<Self>);

    // Предоставляемый метод
    fn wake_by_ref(self: &Rc<Self>) { ... }
}
}

Обязательные методы

wake ⚡️

#![allow(unused)]
fn main() {
fn wake(self: Rc<Self>)
}

Пробудить эту задачу.

Предоставляемые методы

wake_by_ref ⚡️

#![allow(unused)]
fn main() {
fn wake_by_ref(self: &Rc<Self>)
}

Пробудить эту задачу без потребления локального waker.

Если исполнитель поддерживает более дешевый способ пробуждения без потребления waker, он должен переопределить этот метод. По умолчанию он клонирует Rc и вызывает wake на клоне.

Примеры

Это упрощенный пример функций spawn и block_on. Функция spawn используется для добавления новых задач в очередь выполнения, а функция block_on будет удалять их и опрашивать. Когда задача пробуждается, она помещает себя обратно в очередь выполнения для опроса исполнителем.

Примечание: Этот пример жертвует корректностью ради простоты. Реальный пример будет чередовать вызовы poll с вызовами io реактора для ожидания событий вместо циклического опроса.

#![allow(unused)]
#![feature(local_waker)]
fn main() {
use std::task::{LocalWake, ContextBuilder, LocalWaker, Waker};
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::cell::RefCell;
use std::collections::VecDeque;

thread_local! {
    // Очередь, содержащая все задачи, готовые к выполнению
    static RUN_QUEUE: RefCell<VecDeque<Rc<Task>>> = RefCell::default();
}

type BoxedFuture = Pin<Box<dyn Future<Output = ()>>>;

struct Task(RefCell<BoxedFuture>);

impl LocalWake for Task {
    fn wake(self: Rc<Self>) {
        RUN_QUEUE.with_borrow_mut(|queue| {
            queue.push_back(self)
        })
    }
}

fn spawn<F>(future: F)
where
    F: Future<Output=()> + 'static + Send + Sync
{
    let task = RefCell::new(Box::pin(future));
    RUN_QUEUE.with_borrow_mut(|queue| {
        queue.push_back(Rc::new(Task(task)));
    });
}

fn block_on<F>(future: F)
where
    F: Future<Output=()> + 'static + Sync + Send
{
    spawn(future);
    loop {
        let Some(task) = RUN_QUEUE.with_borrow_mut(|queue| queue.pop_front()) else {
            // выходим, так как в очереди больше нет задач
            return;
        };

        // преобразуем Rc<Task> в `LocalWaker`
        let local_waker: LocalWaker = task.clone().into();
        // Строим контекст с помощью `ContextBuilder`
        let mut cx = ContextBuilder::from_waker(Waker::noop())
            .local_waker(&local_waker)
            .build();

        // Опрашиваем задачу
        let _ = task.0
            .borrow_mut()
            .as_mut()
            .poll(&mut cx);
    }
}

block_on(async {
    println!("hello world");
});
}

Совместимость с dyn

Этот трейт не совместим с dyn.

В старых версиях Rust совместимость с dyn называлась "object safety", поэтому этот трейт не является object safe.

Реализаторы

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

Трейт LocalWake автоматически реализуется для:

  • Типов, которые уже реализуют LocalWake
  • Rc<W> где W: LocalWake

Ручные реализации

Разработчики могут реализовать LocalWake для своих кастомных типов:

#![allow(unused)]
#![feature(local_waker)]
fn main() {
use std::rc::Rc;
use std::task::LocalWake;

struct MyLocalWaker;

impl LocalWake for MyLocalWaker {
    fn wake(self: Rc<Self>) {
        // Кастомная логика пробуждения
    }
    
    // Опционально переопределить wake_by_ref для оптимизации
    fn wake_by_ref(self: &Rc<Self>) {
        // Более эффективная реализация без клонирования
    }
}
}

Использование с исполнителями

Создание LocalWaker из типа LocalWake

#![allow(unused)]
#![feature(local_waker)]
fn main() {
use std::rc::Rc;
use std::task::LocalWaker;

let my_local_waker = Rc::new(MyLocalWaker);
let local_waker: LocalWaker = LocalWaker::from(my_local_waker);
}

Интеграция с Context

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

let local_waker = Rc::new(MyLocalWaker).into();
let mut cx = ContextBuilder::from_waker(Waker::noop())
    .local_waker(&local_waker)
    .build();
}

Преимущества перед Wake

  • Отсутствие атомарных операций: использует Rc вместо Arc
  • Поддержка !Send/!Sync типов: может хранить не-потокобезопасные данные
  • Производительность: избегает накладных расходов атомарного подсчета ссылок
  • Гибкость: подходит для однопоточных исполнителей

Ограничения

  • ⚡️ Доступен только в ночных сборках Rust
  • Не может быть использован в многопоточных сценариях
  • Требует включения фичи local_waker

Сравнение с Wake

ХарактеристикаWakeLocalWake
Тип указателяArcRc
ПотокобезопасностьSend + Sync!Send + !Sync
Атомарные операцииДаНет
ПроизводительностьНижеВыше
ПрименениеМногопоточноеОднопоточное

Рекомендации по использованию

  1. Используйте LocalWake для однопоточных исполнителей
  2. Переопределяйте wake_by_ref для избежания клонирования Rc
  3. Комбинируйте с ContextBuilder для гибкого создания контекста
  4. Используйте для оптимизации производительности в single-threaded средах