首页 > 解决方案 > 在 Rust 命令过程中写入 stdio 并从 stdout 读取

问题描述

我将尝试尽可能简化我想要完成的工作,但简而言之,这是我的问题:

我正在尝试将节点外壳生成为 Rust 中的一个进程。我想传递给进程的标准输入 javascript 代码并从进程的标准输出读取 nodejs 输出。这将是一种交互式用法,其中节点 shell 被生成并不断接收 JS 指令并执行它们。

我不希望使用文件参数启动 nodejs 应用程序。

我已经阅读了很多关于 std::process::Command、tokio 以及为什么我们不能使用标准库对管道输入进行读写的内容。我一直在网上看到的一种解决方案(为了在读/写时不阻塞主线程)是使用线程来读取输出。大多数解决方案不涉及连续的写入/读取流程。

我所做的是生成 2 个线程,一个继续写入标准输入,一个继续从标准输出读取。这样,我想,我不会阻塞主线程。但是我的问题是只能主动使用 1 个线程。当我有一个标准输入线程时,标准输出甚至不接收数据。

这是代码,注释应提供更多详细信息

pub struct Runner {
    handle: Child,
    pub input: Arc<Mutex<String>>,
    pub output: Arc<Mutex<String>>,
    input_thread: JoinHandle<()>,
    output_thread: JoinHandle<()>,
}

impl Runner {
    pub fn new() -> Runner {
        let mut handle = Command::new("node")
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .spawn()
            .expect("Failed to spawn node process!");


        // begin stdout thread part
        let mut stdout = handle.stdout.take().unwrap();
        let output = Arc::new(Mutex::new(String::new()));
        let out_clone = Arc::clone(&output);
        let output_thread = spawn(move || loop {
            // code here never executes...why ?
            let mut buf: [u8; 1] = [0];
            let mut output = out_clone.lock().unwrap();

            let what_i_read = stdout.read(&mut buf);
            println!("reading: {:?}", what_i_read);
            match what_i_read {
                Err(err) => {
                    println!("{}] Error reading from stream: {}", line!(), err);
                    break;
                }
                Ok(bytes_read) => {
                    if bytes_read != 0 {
                        let char = String::from_utf8(buf.to_vec()).unwrap();
                        output.push_str(char.as_str());
                    } else if output.len() != 0 {
                        println!("result: {}", output);
                        out_clone.lock().unwrap().clear();
                    }
                }
            }
        });

        // begin stdin thread block
        let mut stdin = handle.stdin.take().unwrap();
        let input = Arc::new(Mutex::new(String::new()));
        let input_clone = Arc::clone(&input);

        let input_thread = spawn(move || loop {
            let mut in_text = input_clone.lock().unwrap();

            if in_text.len() != 0 {
                println!("writing: {}", in_text);
                stdin.write_all(in_text.as_bytes()).expect("!write");
                stdin.write_all("\n".as_bytes()).expect("!write");
                in_text.clear();
            }
        });

        Runner {
            handle,
            input,
            output,
            input_thread,
            output_thread,
        }
    }
    // this function should receive commands
    pub fn execute(&mut self, str: &str) {
        let input = Arc::clone(&self.input);
        let mut input = input.lock().unwrap();

        input.push_str(str);
    }
}

在主线程中,我想将其用作

let mut runner = Runner::new();
runner.execute("console.log('foo'");
println!("{:?}", runner.output);

我还是 Rust 的新手,但至少我通过了借用检查器让我头撞墙的地步,我现在开始觉得它更令人愉悦了 :)

标签: multithreadingruststdoutstdin

解决方案


你是否在线程中设置了一个循环来等待输入?如何等待程序结束。

你现在,如果你这样做

use std::{thread};

fn main() {
    let _output_thread = thread::spawn(move || {
            println!("done");
    });
}

程序的结束有时线程更快,您看不到输出“完成”。

如果你做这样的循环,是一样的

use std::{thread};

fn main() {
    //let output_thread = spawn(move || loop {
            // code here never executes...why ?
    let _output_thread = thread::spawn(move || {
        //-------------------------------------^---
        loop {
            println!("done");
        }
    });
}

如果你等待它工作得很好。

use std::{thread, time};

fn main() {
    //let output_thread = spawn(move || loop {
            // code here never executes...why ?
    let _output_thread = thread::spawn(move || {
        //-------------------------------------^---
        loop {
            println!("done");
        }
    });

    thread::sleep(time::Duration::from_secs(5));
}

使用 2 胎面的一种好方法就像在https://doc.rust-lang.org/std/thread/fn.spawn.html中阐述的那样,使用通道并加入

use std::thread;
use std::sync::mpsc::channel;

let (tx, rx) = channel();

let sender = thread::spawn(move || {
    tx.send("Hello, thread".to_owned())
        .expect("Unable to send on channel");
});

let receiver = thread::spawn(move || {
    let value = rx.recv().expect("Unable to receive from channel");
    println!("{}", value);
});

sender.join().expect("The sender thread has panicked");
receiver.join().expect("The receiver thread has panicked");

推荐阅读