Asynchronous 如何异步处理子流程的I/O?

Asynchronous 如何异步处理子流程的I/O?,asynchronous,rust,subprocess,Asynchronous,Rust,Subprocess,我有一个子进程,它可能在特定的时间内(例如3秒)写入或不写入它的标准输出 如果子流程stdout中的新行以正确的内容开头,我希望返回该行。 最理想的情况是,我想实现如下目标: use std::io::{BufRead, BufReader}; use std::thread; use std::time::Duration; pub fn wait_for_or_exit( reader: &BufReader<&mut std::process::ChildS

我有一个子进程,它可能在特定的时间内(例如3秒)写入或不写入它的标准输出

如果子流程stdout中的新行以正确的内容开头,我希望返回该行。 最理想的情况是,我想实现如下目标:

use std::io::{BufRead, BufReader};
use std::thread;
use std::time::Duration;

pub fn wait_for_or_exit(
    reader: &BufReader<&mut std::process::ChildStdout>,
    wait_time: u64,
    cmd: &str,
) -> Option<String> {
    let signal: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
    let signal_clone = signal.clone();
    let child = thread::spawn(move || {
        thread::sleep(Duration::from_millis(wait_time));
        signal_clone.store(true, Ordering::Relaxed);
    });
    let mut line = String::new();
    while !signal.load(Ordering::Relaxed) {
        //Sleep a really small amount of time not to block cpu
        thread::sleep(Duration::from_millis(10));
        //This line is obviously invalid!
        if reader.has_input() {
            line.clear();
            reader.read_line(&mut line).unwrap();
            if line.starts_with(cmd) {
                return Some(line);
            }
        }
    }
    None
}
使用std::io::{BufRead,BufReader};
使用std::线程;
使用std::time::Duration;
酒吧fn等待或退出(
读者:&BufReader,
等待时间:u64,
cmd:&str,
)->选项{
let信号:Arc=Arc::new(AtomicBool::new(false));
让signal_clone=signal.clone();
让孩子=线程::繁殖(移动| |{
线程::睡眠(持续时间::from_millis(wait_time));
信号存储(true,顺序::Relaxed);
});
让mut line=String::new();
while!signal.load(排序::松弛){
//睡眠时间非常少,不会阻塞cpu
线程::睡眠(持续时间::从_毫秒(10));
//这行显然是无效的!
if reader.has_input(){
line.clear();
reader.read_行(&mut行).unwrap();
如果行以(cmd)开头{
返回一些(行);
}
}
}
没有一个
}
这里唯一不起作用的行是
reader.has\u input()


显然,如果子进程的响应速度比等待时间快得多,并且重复了很多次,那么会有很多休眠线程,但我可以通过通道来解决这个问题。

有两种方法

  • 您可以启动一个单独的线程,然后使用某种机制(可能是一个通道)向等待的线程发出成功或失败的信号
  • 您可以使用您提到的异步IO,例如futures和tokio库 我将两个都演示。我更喜欢期货/东京模式,但如果你不熟悉期货模式,那么选择一可能更好

    Rust stdlib有一个通道API,这个通道实际上有一个可以帮助我们解决很多问题的API

    use std::thread;
    use std::time::Duration;
    use std::sync::mpsc;
    
    // this spins up a separate thread in which to wait for stuff to read
    // from the BufReader<ChildStdout> 
    // If we successfully read, we send the string over the Channel.
    // Back in the original thread, we wait for an answer over the channel
    // or timeout in wait_time secs. 
    pub fn wait_for_or_exit(
        reader: &BufReader<&mut std::process::ChildStdout>,
        wait_time: u64,
        cmd: &str,
    ) -> Option<String> {
        let (sender, receiver) = mpsc::channel();
    
        thread::spawn(move || {
            let line = reader.read_line();
            sender.send(line);
        });
    
        match receiver.recv_timeout(Duration::from_secs(wait_time)) {
            Ok(line) => if line.starts_with(cmd) 
               { Some(line) } else 
               { None },
            Err(mpsc::RecvTimeoutError::Timeout) => None,
            Err(mpsc::RecvTimeoutError::Disconnected) => None  
    
        }
    }
    

    我无法编译选项一。在
    thread::spawn
    中,它抱怨读卡器需要静态生存期。然而,我认为选项一无论如何都不可行,因为底层子进程将被终止并重新启动数百次,并且不能保证它会响应。这将导致数百个侦听器线程,不是吗?我现在正在尝试第二种选择。
    extern crate futures;
    extern crate tokio;
    extern crate tokio_process;
    
    use std::process::Command;
    use std::time::{Duration};
    
    use futures::Future;
    use tokio_process::CommandExt;
    use tokio::prelude::*;
    
    const TIMEOUT_SECS: u64 = 3;
    
    fn main() {
        // Like above, but use `output_async` which returns a future instead of
        // immediately returning the `Child`.
        let output = Command::new("echo").arg("hello").arg("world")
                            .output_async();
    
        let future = output.map_err(|e| panic!("failed to collect output: {}", e))
            .map(|output| {
                assert!(output.status.success());
                assert_eq!(output.stdout, b"hello world\n");
                println!("received output: {}",     String::from_utf8(output.stdout).unwrap());
            })
            .timeout(Duration::from_secs(TIMEOUT_SECS)) // here is where we say we only want to wait TIMETOUT seconds
            .map_err(|_e| { println!("Timed out waiting for data"); });
    
        tokio::run(future);
    }