展开描述
为 Tokio 实现的异步进程管理。
本模块提供了一个 Command 结构体,
其接口模仿标准库中的
std::process::Command 类型,
但提供了创建进程的异步版本函数。这些函数(spawn、status、output 及其变体)
返回“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
- 代表在事件循环上派生的子进程。
- Child
Stderr - 派生子进程的标准错误流。
- Child
Stdin - 派生子进程的标准输入流。
- Child
Stdout - 派生子进程的标准输出流。
- Command
- 此结构体模仿了标准库中
std::process::Command的 API, 但将创建进程的函数替换为异步版本。主要提供的 异步函数是 spawn、status 和 output。