Как я могу читать неблокирующие со стандартного ввода?
Есть ли способ проверить, доступны ли данные на stdin
в Rust, или сделать чтение, которое немедленно возвращает с текущими доступными данными?
Моя цель состоит в том, чтобы иметь возможность читать ввод, полученный, например, с помощью клавиш курсора в оболочке, которая настроена на немедленный возврат всех прочитанных данных. Например, с эквивалентом: stty -echo -echok -icanon min 1 time 0
,
Я полагаю, что одним из решений было бы использование ncurses или подобных библиотек, но я бы хотел избежать любых больших зависимостей.
Пока что я получил только блокировку ввода, а это не то, что я хочу:
let mut reader = stdin();
let mut s = String::new();
match reader.read_to_string(&mut s) {...} // this blocks :(
4 ответа
Большинство операционных систем по умолчанию работают со стандартным вводом и выводом блокирующим образом. Неудивительно, что библиотека Rust следует за ней.
Для чтения из блокирующего потока неблокирующим способом вы можете создать отдельный поток, чтобы дополнительный поток блокировался вместо основного. Проверка, генерирует ли дескриптор блокирующего файла какой-либо ввод, аналогична: порождает поток, заставляет его читать данные, проверяет, выдал ли он какие-либо данные.
Вот фрагмент кода, который я использую с аналогичной целью интерактивной обработки вывода канала и который, мы надеемся, послужит примером. Он отправляет данные по каналу, который поддерживает метод try_recv, позволяющий проверить, доступны ли данные или нет.
Кто-то сказал мне, что mio может использоваться для чтения из канала неблокирующим способом, так что вы можете также проверить это. Я подозреваю, что передача дескриптора файла stdin (0) в PipeReader::from_fd должна просто работать.
Преобразование комментария ОП в ответ:
Вы можете создать поток и отправить данные по каналу. Затем вы можете опросить этот канал в главном потоке, используя try_recv.
use std::io;
use std::sync::mpsc;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::TryRecvError;
use std::{thread, time};
fn main() {
let stdin_channel = spawn_stdin_channel();
loop {
match stdin_channel.try_recv() {
Ok(key) => println!("Received: {}", key),
Err(TryRecvError::Empty) => println!("Channel empty"),
Err(TryRecvError::Disconnected) => panic!("Channel disconnected"),
}
sleep(1000);
}
}
fn spawn_stdin_channel() -> Receiver<String> {
let (tx, rx) = mpsc::channel::<String>();
thread::spawn(move || loop {
let mut buffer = String::new();
io::stdin().read_line(&mut buffer).unwrap();
tx.send(buffer).unwrap();
});
rx
}
fn sleep(millis: u64) {
let duration = time::Duration::from_millis(millis);
thread::sleep(duration);
}
У меня был тот же вопрос, и я получил здесь подходящий для меня ответ , который я скопирую ниже для удобства. Как мне было указано, "tokio
уже поставляется сimpl AsyncRead for Stdin
, поэтому вы сможете сделать что-то вроде":
async fn read_from_stdin_timeout(timeout: Duration) -> io::Result<String> {
let mut buf = String::new();
tokio::time::timeout(timeout, tokio::io::stdin().read_to_string(&mut buf)).await??;
Ok(buf)
}