Proc Async

Running process (asynchronous interface)

class Proc::Async {}

注意: 目前只有 Rakudo 的 MoarVM 后端实现了 [Proc::Async](https://docs.raku.org/type/Proc::Async)

Proc::Async 允许你异步地运行外部命令, 捕获标准输出和错误句柄,并可选择地写入到它的标准输入中。

my $file = 'foo'.IO;
spurt $file, "and\nCamelia\n♡\nme\n";

my $proc = Proc::Async.new: :w, 'tac', '--', $file, '-';
# my $proc = Proc::Async.new: :w, 'sleep', 15; # uncomment to try timeouts 

react {
    whenever $proc.stdout.lines { # split input on \r\n, \n, and \r
        say 'lines: ', $_
    }
    whenever $proc.stderr {
        say 'stderr: ', $_
    }
    whenever $proc.ready {
        say 'PID: ', $_   # Only in Rakudo 2018.04 and newer, otherwise Nil 
    }
    whenever $proc.start {
        say 'Proc finished: exitcode=', .exitcode, ' signal=', .signal;
        down # gracefully jump from the react block
    }
    whenever $proc.print: "I\n♥\nCamelia\n" {
        $proc.close-stdin
    }
    whenever signal(SIGTERM).merge: signal(SIGINT) {
        once {
            say ‘Signal received, asking the process to stop’;
            $proc.kill; # sends SIGHUP, change appropriately 
            whenever signal($_).zip: Promise.in(2).Supply {
                say ‘Kill it!’;
                $proc.kill: SIGKILL
            }
        }
    }
    whenever Promise.in(5) {
        say ‘Timeout. Asking the process to stop’;
        $proc.kill; # sends SIGHUP, change appropriately 
        whenever Promise.in(2) {
            say ‘Timeout. Forcing the process to stop’;
            $proc.kill: SIGKILL
        }
    }
}

say ‘Program finished’;

上面的示例产生以下输出:

line: me
line: ♡
line: Camelia
line: and
line: Camelia
line: ♥
line: I
Proc finished. Exit code: 0
Program finished

或者, 你可以在不使用 react 块的情况下使用 Proc::Async:

# command with arguments 
my $proc = Proc::Async.new('echo', 'foo', 'bar');
 
# subscribe to new output from out and err handles: 
$proc.stdout.tap(-> $v { print "Output: $v" }, quit => { say 'caught exception ' ~ .^name });
$proc.stderr.tap(-> $v { print "Error:  $v" });
 
say "Starting...";
my $promise = $proc.start;
 
# wait for the external program to terminate 
await $promise;
say "Done.";

这产生了下面的输出:

Starting...
Output: foo bar
Done.

打开外部程序进行写入的示例:

use v6;
my $prog = Proc::Async.new(:w, 'hexdump', '-C');
my $promise = $prog.start;
await $prog.write(Buf.new(12, 42));
$prog.close-stdin;
await $promise;

管道几个命令的例子, 例如 echo "Hello, world" | cat -n:

use v6;
my $proc-echo = Proc::Async.new: 'echo', 'Hello, world';
my $proc-cat = Proc::Async.new: 'cat', '-n';
$proc-cat.bind-stdin: $proc-echo.stdout;
await $proc-echo.start, $proc-cat.start;

方法

new 方法

multi method new(*@ ($path, *@args), :$w, :$enc, :$translate-nl --> Proc::Async:D)
multi method new(   :$path, :@args,  :$w, :$enc, :$translate-nl --> Proc::Async:D)

使用外部程序名或路径 $path 和命令行参数 @args 创建一个新的 Proc::Async 对象。

如果 :w 传递给 new,则打开一个到外部程序的标准输入流(stdin)的管道,你可以用 writesay 来写入。

:enc 指定流的编码(仍然可以在各个方法中重写),默认为 utf8

如果 :translate-nl 设置为 True(默认值),则特定于操作系统的换行终止符(例如 Windows 上的 \r\n)将自动转换为 \n

stdout 方法

method stdout(Proc::Async:D: :$bin --> Supply:D)

返回外部程序标准输出流的 Supply。如果 :bin 被传递,则标准输出以二进制形式作为 Blob 传递,否则它被解释为 UTF-8,解码并作为 Str 传递。

my $proc = Proc::Async.new(:r, 'echo', 'Raku');
$proc.stdout.tap( -> $str {
    say "Got output '$str' from the external program";
});
my $promise = $proc.start;
await $promise;

在调用 start 方法之前,必须调用 stdout。否则抛出类 X::Proc::Async::TapBeforeSpawn 的异常。

如果未调用 stdout,则根本不捕获外部程序的标准输出。

请注意,你无法在同一对象上使用和不使用 :bin 调用 stdout;如果你尝试,它将抛出 X::Proc::Async::CharsOrBytes类型的异常。

使用 .Supply 用于合并的 STDOUT 和 STDERR。

stderr 方法

method stderr(Proc::Async:D: :$bin --> Supply:D)

返回外部程序标准错误流的 Supply。如果 :bin 被传递,则标准错误以二进制形式作为 Blob 传递,否则它被解释为 UTF-8,解码并作为 Str 传递。

my $proc = Proc::Async.new(:r, 'echo', 'Raku');
$proc.stderr.tap( -> $str {
    say "Got error '$str' from the external program";
});
my $promise = $proc.start;
await $promise;

在调用 start方法之前,必须调用 stderr。否则抛出类 X::Proc::Async::TapBeforeSpawn 的异常。

如果未调用 stderr,则根本不捕获外部程序的标准错误流。

请注意,你无法在同一对象上使用和不使用 :bin 调用 stderr;如果你尝试,它将抛出 X::Proc::Async::CharsOrBytes 类型的异常。

使用 .Supply 用于合并的 STDOUT 和 STDERR。

bind-stdout 方法

method bind-stdout(IO::Handle:D $handle)

将目标进程的 STDOUT 重定向到句柄(必须打开)。如果 STDOUT 被关闭,将抛出 X::Proc::Async::BindOrUse

my $p = Proc::Async.new("ls", :out);
my $h = "ls.out".IO.open(:w);
$p.bind-stdout($h);
$p.start;

该程序将 ls shell 命令的输出传递给名为 ls.out 的文件,我们打开该文件进行读取。

bind-stderr 方法

method bind-stderr(IO::Handle:D $handle)

将目标进程的 STDERR 重定向到句柄(必须打开)。如果 STDERR 关闭,将抛出 X::Proc::Async::BindOrUse

my $p = Proc::Async.new("ls", "--foo", :err);
my $h = "ls.err".IO.open(:w);
$p.bind-stderr($h);
$p.start;

w 方法

method start(Proc::Async:D: :$scheduler = $*SCHEDULER, :$*CWD --> Promise:D)

开始产生外部程序。返回一个 Promise,一旦外部程序退出,它将与 Proc 对象一起保存,如果程序无法启动,它将被破坏。

如果在之前已调用它的 Proc::Async 对象上调用 start,则抛出类型 X::Proc::Async::AlreadyStarted的异常。

注意:如果您希望 await Promise 并丢弃其结果,请使用

try await $p.start;

如果程序以非零状态退出,则抛出,因为 Proc 返回时,Promise 抛出时抛出,在这种情况下它将在 try 之外沉没。为了避免这种情况,请将其自己沉入 try 中:

try sink await $p.start;

started 方法

method started(Proc::Async:D: --> Bool:D)

.start 已经被调用之前调用则返回 False, 否则返回 True

ready 方法

method ready(Proc::Asnyc:D: --> Promise:D)

一旦进程成功启动则返回一个 kept 状态的 的 Promise。如果程序无法启动,则 Promise 将被破坏。

特定于实现的注释:从 Rakudo 2018.04开始,返回的 promise 将保存进程 ID(PID)。

pid 方法

method pid(Proc::Async:D: --> Promise:D)

相当于 ready

一旦进程成功启动则返回一个 kept 状态的 的 Promise。如果程序无法启动,则 Promise 将被破坏。返回的 promise 将保存进程id(PID)。

特定于实现的说明:从 Rakudo 2018.04 开始提供。

path 方法

method path(Proc::Async:D:)

返回作为第一个参数传递给 new 方法的外部程序的名称和/或路径。

args 方法

method args(Proc::Async:D: --> Positional:D)

返回传递给 new 方法的外部程序的命令行参数。

write 方法

method write(Proc::Async:D: Blob:D $b, :$scheduler = $*SCHEDULER --> Promise:D)

$b 中的二进制数据写入外部程序的标准输入流。

返回一个 Promise,一旦数据完全落在外部程序的输入缓冲区中,它将被保留。

必须创建 Proc::Async 对象以进行写入(使用 Proc::Async.new(:w, $path, @args))。否则抛出 X::Proc::Async::OpenForWriting 异常。

必须在调用方法 write 之前调用 start,否则抛出 X::Proc::Async::MustBeStarted 异常。

method print(Proc::Async:D: Str(Any) $str, :$scheduler = $*SCHEDULER)

$str 中的文本数据写入外部程序的标准输入流,将其编码为 UTF-8。

返回一个 Promise,一旦数据完全落在外部程序的输入缓冲区中,它将被保留。

必须创建 Proc::Async 对象以进行写入(使用 Proc::Async.new(:w, $path, @args))。否则抛出 X::Proc::Async::OpenForWriting 异常。

必须在调用方法 print 之前调用 start,否则抛出 X::Proc::Async::MustBeStarted 异常。

say 方法

method say(Proc::Async:D: $output, :$scheduler = $*SCHEDULER)

$output 上调用方法 gist,添加换行符,将其编码为 UTF-8,并将其发送到外部程序的标准输入流,将其编码为 UTF-8。

返回一个 Promise,一旦数据完全落在外部程序的输入缓冲区中,它将被保留。

必须创建 Proc::Async 对象以进行写入(使用 Proc::Async.new(:w, $path, @args)。否则抛出 X::Proc::Async::OpenForWriting 异常。

必须在调用 say 方法之前调用 start,否则抛出 X::Proc::Async::MustBeStarted 异常。

Supply 方法

multi method Supply(Proc::Async:D: :$bin!)
multi method Supply(Proc::Async:D: :$enc, :$translate-nl)

返回合并的 stdoutstderr 流的 Supply。如果提供了 :$bin 命名参数,则 Supply 将为二进制,生成 Buf 对象,否则,它将处于字符模式,生成 Str 对象并且 :$enc 命名参数可以指定要使用的编码。 :$translate-nl 选项指定是否应转换新的行结尾以匹配当前操作系统使用的那些(例如,Windows上的 \r\n)。

react {
    with Proc::Async.new: «"$*EXECUTABLE" -e 'say 42; note 100'» {
        whenever .Supply { .print } # OUTPUT: «42␤100␤» 
        whenever .start  {}
    }
}

创建二进制和非二进制 .Supply 是一个错误。同时使用 .Supplystderrstdout supplies 也是错误的。

close-stdin 方法

method close-stdin(Proc::Async:D:)

关闭外部程序的标准输入流。从 STDIN 读取的程序通常仅在其输入流关闭时终止。因此,如果等待方法启动的承诺挂起(对于为写入而打开的程序),它可能是一个被遗忘的 close-stdin

必须创建 Proc::Async 对象以进行写入(使用Proc::Async.new(:w, $path, @args))。否则抛出 X::Proc::Async::OpenForWriting 异常。

必须在调用方法 close-stdin 之前调用 start,否则抛出 X::Proc::Async::MustBeStarted 异常。

kill 方法

method kill(Proc::Async:D: $signal = "HUP")

向正在运行的程序发送信号。信号可以是信号名称(“KILL” 或 “SIGKILL”),整数(9)或 Signal enum 的元素(Signal::SIGKILL)。

Async 
comments powered by Disqus