Трейт Wake

Описание

Доступно только на платформах с target_has_atomic=ptr.

Реализация пробуждения задачи на исполнителе.

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

Этот трейт является безопасной с точки зрения памяти и эргономичной альтернативой созданию RawWaker. Он поддерживает распространенный дизайн исполнителей, в котором данные, используемые для пробуждения задачи, хранятся в Arc. Некоторые исполнители (особенно для встроенных систем) не могут использовать этот API, поэтому RawWaker существует как альтернатива для таких систем.

Чтобы создать Waker из некоторого типа W, реализующего этот трейт, оберните его в Arc<W> и вызовите Waker::from() на нем. Также возможно преобразование в RawWaker таким же способом.

Синтаксис

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

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

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

wake

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

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

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

wake_by_ref

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

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

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

Примеры

Базовая функция block_on, которая принимает future и выполняет его до завершения на текущем потоке.

Примечание: Этот пример жертвует корректностью ради простоты. Чтобы предотвратить взаимные блокировки, реализации производственного уровня также должны обрабатывать промежуточные вызовы thread::unpark, а также вложенные вызовы.

#![allow(unused)]
fn main() {
use std::future::Future;
use std::sync::Arc;
use std::task::{Context, Poll, Wake};
use std::thread::{self, Thread};
use core::pin::pin;

/// Waker, который пробуждает текущий поток при вызове.
struct ThreadWaker(Thread);

impl Wake for ThreadWaker {
    fn wake(self: Arc<Self>) {
        self.0.unpark();
    }
}

/// Выполнить future до завершения на текущем потоке.
fn block_on<T>(fut: impl Future<Output = T>) -> T {
    // Закрепляем future, чтобы его можно было опросить.
    let mut fut = pin!(fut);

    // Создаем новый контекст для передачи в future.
    let t = thread::current();
    let waker = Arc::new(ThreadWaker(t)).into();
    let mut cx = Context::from_waker(&waker);

    // Выполняем future до завершения.
    loop {
        match fut.as_mut().poll(&mut cx) {
            Poll::Ready(res) => return res,
            Poll::Pending => thread::park(),
        }
    }
}

block_on(async {
    println!("Hi from inside a future!");
});
}

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

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

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

Реализаторы

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

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

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

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

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

#![allow(unused)]
fn main() {
use std::sync::Arc;
use std::task::Wake;

struct MyWaker;

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

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

Создание Waker из типа Wake

#![allow(unused)]
fn main() {
use std::sync::Arc;
use std::task::Waker;

let my_waker = Arc::new(MyWaker);
let waker: Waker = Waker::from(my_waker);
}

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

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

let waker = Arc::new(MyWaker).into();
let mut cx = Context::from_waker(&waker);
}

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

  • Безопасность памяти: не требует небезопасного кода
  • Эргономичность: простой API с автоматическим управлением памятью
  • Интеграция с Arc: естественно работает со счетчиком ссылок
  • Оптимизация: возможность переопределить wake_by_ref

Ограничения

  • Требует выделения памяти в куче для Arc
  • Доступен только на платформах с атомарными указателями
  • Не подходит для no_std сред без аллокатора

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

  1. Используйте Wake для большинства исполнителей
  2. Переопределяйте wake_by_ref для оптимизации производительности
  3. Используйте RawWaker только когда Wake недоступен
  4. Обеспечьте корректную синхронизацию для многопоточных исполнителей