LINUX.ORG.RU

Как понять асинхонщину в Rust?

 , ,


0

5

В Rust какая-то очень «самобытная» асинхронщина, понять её сложно. Итак, необходимый минимум - реализовать трейт Future. Дольше веселее - есть несколько каких-то малопонятных сущностей, которые за что-то там отвечают, пока непонятно, за что.

Читал кучу мануалов, они из серии: https://sun1-16.userapi.com/impg/cTOihOqKWsJSPD6uBUB6KTQZY823V6f3TzS9Tw/5VAn_b_1JUI.jpg?size=1080x1080&quality=96&proxy=1&sign=3ec0c8687655c3ac555d4b06874cfc49&type=album

Как правильно достать значение из Future? 101 вопрос. Когда Future реализуется сам, а когда его необходимо реализовать самому? Я так понял, логика реализуется в poll(), нет?

Мне нужно, например, читать стдаут порождённого процесса или поток миди-данных в рандомное время на вводе.

Кто распишет вменяемый хеллоуворлд, скину на пиво;)

★★★★★

Вот почему тут не происходит не бельмеса??

use std::future::Future;
use std::task::{Poll, Context};
use std::pin::Pin;
use std::thread;
use std::time::Duration;
use futures::executor::block_on;
use std::io::{self, Write};

struct Counter {
    i: i32
}

impl Copy for Counter { }

impl Clone for Counter {
    fn clone (&self) -> Counter {
        Counter { i:  self.i}
    }
}

impl Future for Counter {
    type Output = String;

    fn poll(self: Pin<&mut Self>, cx: &mut Context <'_>) -> Poll<Self::Output> {
        if self.i < 20 {
            return Poll::Pending;
        } else {
            return Poll::Ready("That's all!".to_string());
        }
    }
}

impl Counter {
    fn new() -> Counter {
        Counter { i: 0 }
    }

    async fn count(&mut self) {
        let mut j = &self.i;
        for j in (0..20).rev() {
            let stdout = io::stdout();
            let mut handle = stdout.lock();
            handle.write_all(b"hello world");
        }
    }
}

fn main() {
    async {
        let mut c = Counter::new();
        block_on(c);
        c.count();
    };
    thread::sleep(Duration::from_millis(20000));
}

meliafaro ★★★★★ ()
Ответ на: комментарий от meliafaro

Ну то есть тред втихую отсиживает своё, не подавая признаков жизни, и тихо умирает, не издав ни звука.

meliafaro ★★★★★ ()
Ответ на: комментарий от anonymous

Для начинающих — как в любом другом языке: при объявлении асинхронной функции пишешь async fn, при вызове асинхронной функции пишешь await.

async fn foo() -> i32 {
    68
}

async fn bar() -> i32 {
    let f = foo().await;
    f.await
}

fn main() {
    let x = bar();
    println!("{}", x);
}

Не-а, не работает. Если бы.

meliafaro ★★★★★ ()
Ответ на: комментарий от anonymous

Вот ты меня уел, и на незнании монад раскусил! Какой нонче анон пошёл образованный.

Ты давай от темы не увиливай: как с «сахарком» эту твою монаду вскрыть в случае острой необходимости?

meliafaro ★★★★★ ()
Ответ на: комментарий от kote4ka

Ну что, продолжишь изучать Rust?

После высеров Царёчка - обязательно!

meliafaro ★★★★★ ()
Ответ на: комментарий от meliafaro

Ну и кто будить фьючу будет? Это же push асинхронность, когда что-то случилось, надо запушить уведомление

q0tw4 ★★★★ ()
Ответ на: комментарий от meliafaro

Для начала надо убедиться, что оно тебе надо. Я уже говорил, что раст - не жабаскрипт, и если можно обойтись без асинка, лучше без него. Он решает довольно специфичный класс проблем (типа огромное число подключений к сереверу), внося при этом дополнительные расходы и неудобства, связанные с тем, что не все библиотеки поддерживают асинхронность. Вот я когда-то хотел грузить модельки в игре через асинк, но потом понял, что оно там лишнее. Выделил поток или несколько для чтения с диска и норм. Ничего нового асинк туда добавить не сможет кроме возможности писать более понятный код, но при желании на мутексах можно такой же по красоте код писать

q0tw4 ★★★★ ()
Ответ на: комментарий от q0tw4

К примеру, у меня есть входной поток сообщений, куда в рандомное время прилетают новые, которые надо парсить, обрабатывать и продолжать вести наблюдение. Сюда бы идеально подошли сигналы/слоты GTK/Qt, но они медленные даже по заверениям их разработчиков. У меня таких событий может быть до сотни в секунду по самым скромным подсчётам, а скорее, значительно больше. Всё это расчитано не на серверное железо, а на обычный десктоп/лэптоп. Поэтому я и ищу более производительное решение, которое бы реализовало тот же кейс.

В принципе, у меня есть опыт работы с асинхронными функциями в GTK/GIO, они примерно так и работают - даётся задание, выполняющееся асинхронно, в конце вызывается коллбэк, обрабатывающий результат работы асинхронной функции. В С++ тоже всё проще, там из футуры можно достать результат одним методом. Здесь всё как-то намного сложнее, а учебник даёт очень мало инфы.

meliafaro ★★★★★ ()
Ответ на: комментарий от meliafaro

С этим кодом много проблем.

async это такой сахар чтобы обозначить что функция или блок возвращает что-то что реализует трейт Future. Его есть смысл писать только если внутри есть await.

let mut j = &self.i; здесь ссылка неизменяемая, нужно let mut j = &mut self.i;

Ничего не делает потому что фючи ничего не делают если их не полить. Вот это вот выражение

    async {
        let mut c = Counter::new();
        block_on(c);
        c.count();
    };

Возвращает значение, реализующее Future. Ты с ним ничего не делаешь.

В любом случае, даже если ты починишь что я здесь описал, это не приблизит тебя к результату.

vlad9486 ()
Последнее исправление: vlad9486 (всего исправлений: 1)
Ответ на: комментарий от vlad9486

Спасибо.

То есть я правильно понимаю, что простого способа вытащить результат из футуры нет? Вот тут чувак на несколько десяток строк расписал обработчик футур, как я понимаю, без этого вообще никак? https://github.com/spacejam/extreme

meliafaro ★★★★★ ()
Ответ на: комментарий от meliafaro

Вытаскивать значение не нужно. Пусть оно всегда будет в какой-то футуре. Скармливаешь рантайму футуру, которая реализует все твое приложение и внутри все делаешь. Для этого в токио есть макрос.

[tokio::main]
async fn main() {
    // ...
}

Он разворачивается во что-то типа

fn main() {
    tokio::runtime::Runtime::new().unwrap().spawn(async {
        // ...
    });
}

Если прямо нужно понять как можно было бы подождать, то попробуй

let mut f = async {
    // code
};
let mut f = unsafe { Pin::new_unchecked(&mut f) };

loop {
    match f.poll() {
        Poll::Pending => (),
        Poll::Ready(x) => break x,
    }
}

Этот цикл вернет x – то, что возвращает тот асинк блок.

vlad9486 ()
Последнее исправление: vlad9486 (всего исправлений: 1)
Ответ на: комментарий от meliafaro

Я сам когда-то писал рантайм-планировщик для обучения. Но забросил, когда понял что также оптимально как tokio сделать очень трудно.

Статья в тему https://tokio.rs/blog/2019-10-scheduler

vlad9486 ()
Ответ на: комментарий от vlad9486

Увы, вот это работать не будет:

f.poll()
Нужен аргумент Context в метод poll(). А он создаётся довольно гемморно. Ну то есть это оправданно для большого приложения, но в хеллоуворлдиках пути нет. Ещё всё это привязано к циклу, это тоже неудобно. Приколько реализовано в GIO из Glib: запускаешь async-функцию, как она отработает, просто шлёт коллбэк с результатом.

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

meliafaro ★★★★★ ()
Ответ на: комментарий от q0tw4

Спасибо. Один из немногих здесь стоящих ответов.

meliafaro ★★★★★ ()
Ограничение на отправку комментариев: только для зарегистрированных пользователей