跳到主要内容

Module process

搜索

Module process 

Source
展开描述

为 Tokio 实现的异步进程管理。

本模块提供了一个 Command 结构体, 其接口模仿标准库中的 std::process::Command 类型, 但提供了创建进程的异步版本函数。这些函数(spawnstatusoutput 及其变体) 返回“future aware”类型,可与 Tokio 互操作。 异步进程支持在 Unix 上通过信号处理实现, 在 Windows 上则使用系统 API。

§示例

下面是一个将派生 echo hello world 并等待其完成的示例程序。

use tokio::process::Command;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // The usage is similar as with the standard library's `Command` type
    let mut child = Command::new("echo")
        .arg("hello")
        .arg("world")
        .spawn()
        .expect("failed to spawn");

    // Await until the command completes
    let status = child.wait().await?;
    println!("the command exited with: {}", status);
    Ok(())
}

接下来,让我们看一个不仅派生 echo hello world、还捕获其输出的示例。

use tokio::process::Command;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Like above, but use `output` which returns a future instead of
    // immediately returning the `Child`.
    let output = Command::new("echo").arg("hello").arg("world")
                        .output();

    let output = output.await?;

    assert!(output.status.success());
    assert_eq!(output.stdout, b"hello world\n");
    Ok(())
}

我们也可以按行读取输入。

use tokio::io::{BufReader, AsyncBufReadExt};
use tokio::process::Command;

use std::process::Stdio;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut cmd = Command::new("cat");

    // Specify that we want the command's standard output piped back to us.
    // By default, standard input/output/error will be inherited from the
    // current process (for example, this means that standard input will
    // come from the keyboard and standard output/error will go directly to
    // the terminal if this process is invoked from the command line).
    cmd.stdout(Stdio::piped());

    let mut child = cmd.spawn()
        .expect("failed to spawn command");

    let stdout = child.stdout.take()
        .expect("child did not have a handle to stdout");

    let mut reader = BufReader::new(stdout).lines();

    // Ensure the child process is spawned in the runtime so it can
    // make progress on its own while we await for any output.
    tokio::spawn(async move {
        let status = child.wait().await
            .expect("child process encountered an error");

        println!("child status was: {}", status);
    });

    while let Some(line) = reader.next_line().await? {
        println!("Line: {}", line);
    }

    Ok(())
}

下面是另一个示例,使用 sort 向子进程的标准输入写入内容,并捕获排序后的输出。

use tokio::io::AsyncWriteExt;
use tokio::process::Command;

use std::process::Stdio;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut cmd = Command::new("sort");

    // Specifying that we want pipe both the output and the input.
    // Similarly to capturing the output, by configuring the pipe
    // to stdin it can now be used as an asynchronous writer.
    cmd.stdout(Stdio::piped());
    cmd.stdin(Stdio::piped());

    let mut child = cmd.spawn().expect("failed to spawn command");

    // These are the animals we want to sort
    let animals: &[&str] = &["dog", "bird", "frog", "cat", "fish"];

    let mut stdin = child
        .stdin
        .take()
        .expect("child did not have a handle to stdin");

    // Write our animals to the child process
    // Note that the behavior of `sort` is to buffer _all input_ before writing any output.
    // In the general sense, it is recommended to write to the child in a separate task as
    // awaiting its exit (or output) to avoid deadlocks (for example, the child tries to write
    // some output but gets stuck waiting on the parent to read from it, meanwhile the parent
    // is stuck waiting to write its input completely before reading the output).
    stdin
        .write(animals.join("\n").as_bytes())
        .await
        .expect("could not write to stdin");

    // We drop the handle here which signals EOF to the child process.
    // This tells the child process that it there is no more data on the pipe.
    drop(stdin);

    let op = child.wait_with_output().await?;

    // Results should come back in sorted order
    assert_eq!(op.stdout, "bird\ncat\ndog\nfish\nfrog\n".as_bytes());

    Ok(())
}

通过一些协调工作,我们还可以把一个命令的输出通过管道传给另一个命令。

use tokio::join;
use tokio::process::Command;
use std::process::Stdio;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut echo = Command::new("echo")
        .arg("hello world!")
        .stdout(Stdio::piped())
        .spawn()
        .expect("failed to spawn echo");

    let tr_stdin: Stdio = echo
        .stdout
        .take()
        .unwrap()
        .try_into()
        .expect("failed to convert to Stdio");

    let tr = Command::new("tr")
        .arg("a-z")
        .arg("A-Z")
        .stdin(tr_stdin)
        .stdout(Stdio::piped())
        .spawn()
        .expect("failed to spawn tr");

    let (echo_result, tr_output) = join!(echo.wait(), tr.wait_with_output());

    assert!(echo_result.unwrap().success());

    let tr_output = tr_output.expect("failed to await tr");
    assert!(tr_output.status.success());

    assert_eq!(tr_output.stdout, b"HELLO WORLD!\n");

    Ok(())
}

§注意事项

§断开/取消

与标准库的行为类似,不同于 futures 范式的“drop 即取消”, 默认情况下,派生出的进程即使在 Child 句柄被 drop 之后 仍会继续执行。

Command::kill_on_drop 方法可用于修改此行为, 当 Child 包装器在子进程退出前被 drop 时终止子进程。

§Unix 进程

在 Unix 平台上,进程在退出之后必须被父进程“收藏”(reap)以释放所有的 OS 资源。已退出但还未被父进程收藏的子进程被称为“僵尸”进程。 这种进程会继续占用系统实施的资源限制, 僵尸进程过多会阻禁另外派生新进程。

tokio 运行时将尽力尝试回收并清理它派生的任何进程。 但尝试的频率和速度不提供任何额外保证。

如果需要更严格的清理保证,建议在完全 await 完毕之前,避免 drop Child 进程句柄。

结构体§

Child
代表在事件循环上派生的子进程。
ChildStderr
派生子进程的标准错误流。
ChildStdin
派生子进程的标准输入流。
ChildStdout
派生子进程的标准输出流。
Command
此结构体模仿了标准库中 std::process::Command 的 API, 但将创建进程的函数替换为异步版本。主要提供的 异步函数是 spawnstatusoutput