声明
本章翻译仅用于 Raku 学习和研究, 请支持电子版或纸质版。
第十八章. Supplies, Channels 和 Promises
Supplies and channels provide ways to send data from one part of a program to another. A Supply
is a direct line of communication from a source of data to possibly many consumers. A Channel
allows any part of your program to add data to a queue that any part of your program can read.
Supplies 和 channels 提供了将数据从程序的一部分发送到另一个程序的方法。 Supply
是从数据源到可能许多消费者的直接通信线。 Channel
允许程序的任何部分将数据添加到程序的任何部分都可以读取的队列中。
A Promise
allows code to run asynchronously (concurrently)—different bits of code can run in overlapping time frames. This is quite handy while employing either Supply
s or Channel
s (or both).
Promise
允许代码异步(并发)运行 - 不同的小片代码可以在重叠的时间帧中运行。使用 Supply
s或 Channel
s(或两者)时,这非常方便。
Supplies
A Supplier
emits a message to every Supply
that has asked to receive its messages. This happens asynchronously; they do their work as your program does other things. You can process things in the background and handle the results as they come in rather than stopping the entire program to wait for all of the data. Other languages may call this “Publish–Subscribe” (or “PubSub”).
Supplier
向要求接收其消息的每个 Supply
发出消息。这是异步发生的;当程序做其它的事情时它们完成他们的工作。你可以在后台处理事物并在结果进入时处理结果,而不是停止整个程序等待所有数据。其他语言可称之为“发布 - 订阅”(或“PubSub”)。
Here’s a useless example. Set up a Supplier
and call .emit
to send a message. Since you didn’t define any Supply
s that message goes nowhere; it’s gone forever:
这是一个无用的例子。设置 Supplier
并调用 .emit
发送消息。由于你没有定义任何 Supply
,消息无处可用, 它永远消失了:
my $supplier = Supplier.new;
$supplier.emit: 3;
To receive that message ask the Supplier
for a Supply
(yes, the terminology is a bit thick) by calling .tap
with a Block
:
要接收该消息,请通过调用 .tap
来要求 Supplier
提供 Supply
(是的,术语有点多):
my $supplier = Supplier.new;
my $supply = $supplier.Supply;
my $tap = $supply.tap: { put "$^a * $^a = ", $^a**2 };
$supplier.emit: 3;
The Supply
receives the 3
and passes that as the argument to the Block
, which then outputs the message:
Supply
接收 3
并将其作为参数传递给 Block
,然后 Block 输出消息:
3 * 3 = 9
There are some useful builtin Supply
s available. The .interval
Supply
factory automatically emits the next ordinal number at the number of seconds (possibly fractional) you specify. You don’t specify the Supplier
because that’s handled for you:
有一些有用的内置 Supply
可用。 .interval
Supply
工厂会自动以指定的秒数(可能是小数)发出下一个序号。你没有指定 Supplier
,因为已经为你处理好了:
my $fifth-second = Supply.interval: 0.2;
$fifth-second.tap: { say "First: $^a" };
sleep 1;
The output shows five lines. Why only five? There are five-fifths of a second until the program ends once the sleep
finishes:
输出显示五行。为什么只有五行?一旦 sleep
结束,程序结束时有五个零点二秒的时间:
First: 0
First: 1
First: 2
First: 3
First: 4
Once you start the tap it continues to handle values asynchronously until the program ends (or you turn off the tap). Two things happen once your program reaches the sleep
statement. First, the program waits the amount of time that you specified. Second, the Supplier
emits values that the tap handles. Those two things happen concurrently. As you sleep the Supplier
is still working. All that in a couple of lines of code!
一旦你开始点击它继续异步处理值,直到程序结束(或你关闭水龙头 tap)。一旦程序到达 sleep
语句,就会发生两件事。首先,程序会等待指定的时间。其次, Supplier
会发出 tap 处理的值。这两件事同时发生。当你睡眠时, Supplier
仍然在工作。所有这些几行代码就完成了!
注意
Concurrency isn’t parallelism. Concurrency allows two different things to progress during overlapping time frames. Parallelism means that two different things happen at the exact same time. People tend to be fuzzy with their definitions, though.
并发不是并行。并发允许在重叠时间帧期间处理两个不同的事情。并行意味着两个不同的事情在同一时间发生。不过,人们往往对其定义模糊不清。
If you took out the sleep
statement you wouldn’t get any output—the program would end right away. The Supplier
doesn’t keep the program going. If you increase the sleep
time to make the program run longer you get more output.
如果你拿走 sleep
语句,你将无法获得任何输出 - 程序将立即结束。Supplier
不会让程序继续运行。如果增加睡眠时间以使程序运行更长时间,则可获得更多输出。
Here’s a counter that will loop forever but only makes one line. The carriage return goes back to the beginning of the line but doesn’t advance the line (terminal buffering might interfere though):
这是一个永远循环但只产生一行的计数器。回车返回到行的开头但不推进行(终端缓冲可能会干扰):
my $fifth-second = Supply.interval: 0.2;
$fifth-second.tap: { print "\x[D]$^a" };
loop { }
Multiple Taps
You aren’t limited to one tap; you can have as many as you like on the same Supply
. This program will take two seconds to finish. The first tap will run for two seconds and the second tap will run for the last second:
你不仅限于一个水龙头(tap); 你可以在同一个 Supply
上拥有任意数量的 tap。该程序将需要两秒钟才能完成。第一次点击将运行两秒钟,第二次点击将运行最后一秒:
my $supply = Supply.interval: 0.5;
$supply.tap: { say "First: $^a" };
sleep 1;
$supply.tap: { say "Second: $^a" };
sleep 1;
Each tap labels its output:
每个点击(tap)标记其输出:
First: 0
First: 1
Second: 0
First: 2
First: 3
Second: 1
Notice anything strange here? The second tap started at 0 again instead of getting the same number as the first tap got at the same time. The .interval
method creates an on-demand supply. It starts to produce values when a tap asks for them and it generates the interval fresh for each new tap. Each time a tap wants a value it gets the next one in line, independently of any other taps.
注意这里有什么奇怪的东西吗?第二次点击再次从0开始,而不是获得与第一次点击相同的数字。 .interval
方法创建按需供应(supply)。当水龙头要求它们时它开始产生值,并且它为每个新水龙头(tap)产生新鲜的间隔。每次点击(tap)想要一个值时,它就会获得下一个值,与任何其他点击无关。
The code in a tap
must completely finish before that code runs again with another value. This ensures that your code doesn’t get confused when it has persistent variables. If this Block
ran again before the first run finished then the value of $n
would increment a couple of times before the first run could output its message:
在该代码再次使用其他值运行之前,tap中的代码必须完全完成。这可确保你的代码在具有持久变量时不会混淆。如果此 Block
在第一次运行完成之前再次运行,则 $n
的值将在第一次运行输出其消息之前增加几次:
$supply.tap: {
state $n = 0; $n++;
sleep 1; # misses a couple of emitted values!
say "$n: $^a"
};
EXERCISE 18.1Create a Supplier
that emits lines of input. Tap that so that you output only the names you have not seen previously. You might use the butterfly census file from the Downloads section of the website.
练习18.1创建一个发出输入行的 Supplier
。点击它,这样你只输出以前没有见过的名字。你可以使用 website下载部分的蝴蝶人口普查文件。
Live Supplies
A live supply is different from the on-demand ones you’ve encountered so far. It emits a single stream of values that all taps share. When a new value is available the old one is discarded even if no tap has read it. Each new tap starts with the current value from that single stream. Turn an on-demand supply into a live supply with .share
:
实时供应(supply)与你目前遇到的按需供应(supply)不同。它会发出所有分流器(taps)共享的单个值流。当有新值可用时,即使没有点击(tap)已读取旧值,也会丢弃旧值。每个新点击(tap)都以该单个流的当前值开始。使用 .share
将按需供应(supply)转变为实时供应(supply):
my $supply = Supply.interval(0.5).share;
$supply.tap: { say "First: $^a" };
sleep 1;
$supply.tap: { say "Second: $^a" };
sleep 1;
The output is different in two ways. First, the 0
value is missing. The Supply
emitted that before the first tap had a chance to see it. After one second the second tap starts and the Supply
emits 2
; both taps see 2
. After that both taps continue to see the same values until the end of the program:
输出在两个方面有所不同。首先,缺少 0
值。在第一次 tap 有机会看到它之前,Supply
就发出了它。一秒钟后,第二次点击开始, Supply
发出 2
;两个水龙头(tap)都看到 2
.之后,两个水龙头(tap)在程序结束前继续看到相同的值:
First: 1
First: 2
Second: 2
First: 3
Second: 3
First: 4
Second: 4
When you no longer need a tap you can close it; it will no longer receive values:
当你不再需要水龙头(tap)时,你可以关闭它;它将不再接收值:
my $supply = Supply.interval(0.4).share;
my $tap1 = $supply.tap: { say "1. $^a" };
sleep 1;
my $tap2 = $supply.tap: { say "2. $^a" };
sleep 1;
$tap2.close;
sleep 1;
At the start the first tap is handling everything. The second tap starts after the first sleep
finishes. Then both taps handle things for a second, then the first tap closes and it’s only the second tap still working:
在开始时,第一个水龙头处理一切。第一次睡眠结束后,第二次点击(tap)开始。然后两个水龙头(taps)都处理了一秒钟,然后第一个水龙头(tap)关闭,只有第二个水龙头(tap)仍在工作:
First: 1
First: 2
First: 3
Second: 3
First: 4
Second: 4
Second: 5
Second: 6
Second: 7
So far this section has dealt with only Supply
s that you created. Many other objects can provide a Supply
. The .lines
method returns a Seq
which can turn into a Supply
:
到目前为止,本节仅涉及你创建的 Supply
。许多其他对象可以提供 Supply
。 .lines
方法返回一个可以转变成 Supply
的 Seq
:
my $supply = $*ARGFILES.lines.Supply; # IO::ArgFiles
$supply.tap: { put $++ ~ ": $^a" };
$supply.tap: {
state %Seen;
END { put "{%Seen.keys.elems} unique lines" }
%Seen{$^a}++;
};
Most things that are List
s (or can turn into List
s) can do this:
大多数列表(或可以转变成列表)可以做到这一点:
my $list = List.new: 1, 4, 9, 16;
my $supply = $list.Supply;
$supply.tap: { put "Got $^a" }
Even an infinite sequence will work:
即使是无限序列也会起作用:
my $seq := 1, 2, * + 1 ... *;
my $supply2 = $seq.Supply;
$supply2.tap: { put "Got $^a" }
Notice that these examples don’t need a sleep
to delay the end of the program. They aren’t “on the clock” like .interval
; they go through each of their values.
请注意,这些示例不需要 sleep
来延迟程序结束。它们不像 .interval
那样“在时钟上”;他们遍历了每一个值。
EXERCISE 18.2Create a live Supply
that emits a number every second. After three seconds, tap it and output the number it emitted. After another three seconds, tap it again to output the same thing. Wait three more seconds, then close the second tap. Finally, after another three seconds close the first tap.
练习18.2创建一个每秒发出一个数字的实时供应(Supply
)。三秒钟后,点击(tap)它并输出它发出的数字。再过三秒钟,再次点击(tap)它输出相同的东西。再等三秒钟,然后关闭第二个水龙头(tap)。最后,再过三秒后关闭第一个水龙头(tap)。
Channels
Channel
s are first-come, first-served queues. They ensure that something is processed exactly once. Anything can put thingys into the channel and anything can take thingys off the channel. The code on either side of the Channel
doesn’t need to know about the other. Several threads can share a Channel
, but once something asks for the next thingy that thingy disappears from the Channel
and can’t be processed by other code.
Channel
s 是先到先得的队列。他们确保东西只被准确地处理一次。任何东西都可以将东西放入 Channel,任何东西都可以从 Channels 中将东西拿走。Channel
两侧的代码无需了解另一方。几个线程可以共享一个 Channel
,但是一旦有东西要求下一个东西,那个东西就会从 Channel
中消失而不能被其他代码处理。
Create a Channel
. Add to it with .send
and take a thingy with .receive
. When you are done with the Channel
, .close
it:
创建一个 Channel
。使用 .send
添加并使用 .receive
接收。处理完 Channel
后,关闭(close
)它:
my $channel = Channel.new;
$channel.send: 'Hamadryas';
put 'Received: ', $channel.receive;
$channel.close;
The output shows the value you added:
输出显示你添加的值:
Received: Hamadryas
After the .close
you can’t send more values to the Channel
. Anything you’ve already added is still in the Channel
and available to process. You can .receive
until the Channel
is empty:
在关闭(.close
) Channel 之后,你无法向 Channel
发送更多值。你已添加的任何内容仍在 Channel
中,可供处理。你可以 .receive
直到 Channel
为空:
my $channel = Channel.new;
$channel.send: $_ for <Hamadryas Rhamma Melanis>;
put 'Received: ', $channel.receive;
$channel.close; # no more sending
while $channel.poll -> $thingy {
put "while received $thingy";
}
The while
uses .poll
instead of .receive
. If there is a thingy, .poll
returns it. If there are no more thingys currently available it returns Nil
(ending the looping):
while
使用 .poll
而不是 .receive
。如果有东西,.poll
会返回它。如果当前没有更多东西可用,则返回Nil
(结束循环):
Received: Hamadryas
while received Rhamma
while received Melanis
When .poll
returns Nil
you don’t know if there will ever be more thingys available. If the Channel
is still open something can add more thingys; if the Channel
is closed there will never be anything more to .receive
. Calling .fail
closes the Channel
, and .receive
will throw an error if you call it again. You can CATCH
the Exception
to end the loop:
当 .poll
返回 Nil
时,你不知道是否会有更多可用的东西。如果 Channel
仍处于打开状态,可以添加更多东西; 如果 Channel
关闭,将永远不会有更多的东西可以接收(.receive
)。调用 .fail
会关闭 Channel
,如果再次调用,则 .receive
会抛出错误。你可以捕获异常以结束循环:
my $channel = Channel.new;
$channel.send: $_ for <Hamadryas Rhamma Melanis>;
put 'Received: ', $channel.receive;
$channel.fail('End of items'); # X::AdHoc
loop {
CATCH {
default { put "Channel is closed"; last }
}
put "loop received: ", $channel.receive;
}
Instead of a loop you can tap
the Channel
; it calls .receive
for you:
你可以点击(tap
) Channel
代替 loop 循环;它为你调用 .receive
:
my $channel = Channel.new;
$channel.send: $_ for <Hamadryas Rhamma Melanis>;
put 'Received: ', $channel.receive;
$channel.fail('End of items');
$channel.Supply.tap: { put "Received $_" }
CATCH { default { put "Channel is closed" } }
The output is the same either way:
输出相同:
Received: Hamadryas
loop received: Rhamma
loop received: Melanis
Channel is closed
EXERCISE 18.3Create a Channel
and tap it. Send lines of input to the Channel
but only print the ones with prime line numbers.
练习18.3创建一个 Channel
并点按(tap)它。将输入行发送到 Channel
但仅打印具有主要行号的输入。
Promises
A Promise
is a bit of code that will produce a result sometime later, and that later might not be soon. It schedules work to happen in another thread while the rest of your program moves on. These are the underpinnings of Raku’s concurrency and they do most of the hard work for you.
Promise
是一小片会在稍后产生结果的代码,所说的"稍后"可能不会很快。它安排工作在另一个线程中发生,而程序的其余部分继续。这些是 Raku 并发性的基础,它们为你完成了大部分艰苦的工作。
Every Promise
has a status. It might be waiting to run, currently running, or finished. How it finishes decides its status: a Promise
is Kept
when it succeeds or Broken
when it fails. While it’s working it’s Planned
.
每个 Promise
都有一个状态。它可能正在等待运行,当前正在运行或已完成。它如何完成决定它的状态:Promise
在成功时是 Kept
,在失败时是 Broken
。当它正在运行时是 Planned
。
A simple Promise
is a timer. The .in
method makes a Promise
that will be kept after the number of seconds you specify:
一个简单的 Promise
是一个计时器。 .in
方法生成一个 Promise
,它将在你指定的秒数后变成 kept 状态:
my $five-seconds-from-now = Promise.in: 5;
loop {
sleep 1;
put "Promise status is: ", $five-seconds-from-now.status;
}
At first the Promise
is Planned
. After five seconds (roughly) the Promise
converts to Kept
. At that point you know that five seconds have passed:
起初 Promise
是 Planned
。五秒钟后(大致), Promise
转换为 Kept
。那时你知道已经过了五秒钟:
Promise status is: Planned
Promise status is: Planned
Promise status is: Planned
Promise status is: Planned
Promise status is: Kept
Promise status is: Kept
...
You don’t need to continually check the Promise
. Use .then
to set up code to run when it is kept:
你不需要不断检查 Promise
。使用 .then
设置代码在 kept
时运行:
my $five-seconds-from-now = Promise.in: 5;
$five-seconds-from-now.then: { put "It's been 5 seconds" };
Nothing happens when you run this program; the Promise
isn’t kept before the program ends. Planned Promise
s don’t prevent the program from ending.
运行此程序时没有任何反应;在程序结束之前不保留 Promise
。计划的 Promise
不会阻止程序结束。
You could give your program enough time for five seconds to elapse. A sleep
extends the program time:
你可以给你的程序足够的时间,让你的时间过去五秒钟。睡眠(sleep
)延长了程序时间:
my $five-seconds-from-now = Promise.in: 5;
$five-seconds-from-now.then: { put "It's been 5 seconds" };
sleep 7;
Now you see the output from the code in .then
:
现在你看到 .then
中代码的输出:
It's been 5 seconds
Waiting for Promises
Instead of sleeping (and guessing the time you need to be idle), you can use await
, which blocks your program until the Promise
is either kept or broken:
你可以使用 await
而不是 sleep
(并猜测你需要空闲的时间)来阻塞你的程序,直到 Promise
被保留或损坏:
my $five-seconds-from-now = Promise.in: 5;
$five-seconds-from-now.then: { put "It's been 5 seconds" };
await $five-seconds-from-now;
These examples use await
because you need the program to keep running. In something more interesting your program is likely doing a lot of other work, so you might not need to keep the program alive.
这些示例使用 await
,因为你需要程序继续运行。更有趣的是,你的程序很可能会做很多其他的工作,所以你可能不需要让程序活着。
Instead of a relative time you can use .at
with an absolute time. That could be an Instant
value or something that you can coerce to an Instant
(or a Numeric
value that represents an Instant
):
你可以使用绝对时间的 .at
而不是相对时间。这可以是 Instant
值或者你可以强制为 Instant
(或表示 Instant
的数值)的值:
my $later = Promise.at: now + 7;
$later.then: { put "It's now $datetime" };
await $later;
The start
keyword creates a Promise
. When the code completes the Promise
is finished:
start
关键字创建一个 Promise
。代码完成后, Promise
完成:
my $pause = start {
put "Promise starting at ", now;
sleep 5;
put "Promise ending at ", now;
};
await $pause;
The output shows the start and end of the Promise
:
输出显示 Promise
的开始和结束:
Promise starting at Instant:1507924913.012565
Promise ending at Instant:1507924918.018444
A Promise
is broken if it throws an Exception
. You can return all the False
values you like, but until youfail
or throw an Exception
with an error your Promise
will be kept. This succeeds even though it returns False
:
如果 Promise
抛出异常,它就会被破坏。你可以返回你喜欢的所有 False
值,但是直到你失败或抛出一个带有错误的 Exception,你的 Promise
将被保留。即使它返回 False
,这也会成功:
my $return-false = start {
put "Promise starting at ", now;
sleep 5;
put "Promise ending at ", now;
return False; # still kept
};
await $return-false;
This example breaks the Promise
because you explicitly fail
:
下面这个示例打破了 Promise
,因为你显式地调用了 fail
:
my $five-seconds-from-now = start {
put "Promise starting at ", now;
sleep 5;
fail;
put "Promise ending at ", now;
};
await $five-seconds-from-now;
You get part of the output, but the fail
stops that Block
before you get the rest of the output:
你获得了输出的一部分,但 fail
会在你获得其余输出之前停止该 Block
:
Promise starting at Instant:1522698239.054087
An operation first awaited:
in block <unit> at ...
Died with the exception:
Failed
in block at ...
Waiting for Multiple Promises
The Await
can take a list of Promise
s:
Await
可以接收一个 Promise
列表:
put "Starting at {now}";
my @promises =
Promise.in( 5 ).then( { put '5 finished' } ),
Promise.in( 3 ).then( { put '3 finished' } ),
Promise.in( 7 ).then( { put '7 finished' } ),
;
await @promises;
put "Ending at {now}";
The program doesn’t end until all of the Promises are kept:
在所有的 Promise
变成 kept 状态之前,程序不会结束:
Starting at Instant:1524856233.733533
3 finished
5 finished
7 finished
Ending at Instant:1524856240.745510
If any of the Promise
s are broken then the entire await
is done and the planned Promise
s are abandoned:
如果任何一个 Promise
被破坏,则整个 await
完成并且计划的 Promise
被放弃:
put "Starting at {now}";
my @promises =
start { sleep 5; fail "5 failed" },
Promise.in( 3 ).then( { put '3 finished' } ),
Promise.in( 7 ).then( { put '7 finished' } ),
;
await @promises;
put "Ending at {now}";
If the .in( 3 )
Promise
is kept then the one with start
fails:
如果 .in( 3 )
Promise
变为 kept 状态 ,那么带有 start
的那个 Promise 失败:
Starting at Instant:1524856385.367019
3 finished
An operation first awaited:
in block <unit> at await-list.p6 line 9
Died with the exception:
5 failed
in block at await-list.p6 line 4
Managing Your Own Promises
In the previous examples there was something else managing the Promise
s for you. You can do that all yourself. Start by making a bare Promise
:
在前面的例子中,还有其他东西为你管理 Promise
。你可以自己做。首先制作一个裸的 Promise
:
my $promise = Promise.new;
Check its status by smart matching against the constants from PromiseStatus
(which you get for free):
通过与 PromiseStatus
(你免费获得)的常量进行智能匹配来检查其状态:
put do given $promise.status {
when Planned { "Still working on it" }
when Kept { "Everything worked out" }
when Broken { "Oh no! Something didn't work" }
}
At this point $promise
is planned and will stay that way. This will loop forever:
在这一点上,$promise
计划并将保持这种方式。这将永远循环:
loop {
put do given $promise.status {
when Planned { "Still working on it" }
when Kept { "Everything worked out" }
when Broken { "Oh no! Something didn't work" }
}
last unless $promise.status ~~ Planned;
sleep 1;
}
You can use now
to note the start time and to check that if it’s five seconds later to make your own .at
or .in
. Some time after five seconds you call .keep
to change the status:
你现在可以使用 now
来记录开始时间,并检查是否在五秒后制作你自己的 .at
或 .in
。五秒后的一段时间,你可以调用 .keep
改变状态:
my $promise = Promise.new;
my $start = now;
loop {
$promise.keep if now > $start + 5;
given $promise.status {
when Planned { put "Still working on it" }
when Kept { put "Everything worked out" }
when Broken { put "Oh no! Something didn't work" }
}
last unless $promise.status ~~ Planned;
sleep 1;
}
Now the loop stops after five seconds:
现在循环在五秒后停止:
Still working on it
Still working on it
Still working on it
Still working on it
Still working on it
Everything worked out
This Promise
can still call code with .then
:
这个 Promise
仍然可以使用 .then
调用代码:
my $promise = Promise.new;
$promise.then: { put "Huzzah! I'm kept" }
my $start = now;
loop { ... } # same as before
The output shows the output from the .then
code:
输出显示 .then
代码的输出:
Still working on it
Still working on it
Still working on it
Still working on it
Still working on it
Everything worked out
Huzzah! I'm kept
Or you might break the Promise
. Either way your .then
code runs, and you need to distinguish between those cases. The .then
code has one argument; that’s the Promise
itself. If you don’t name the argument it’s in $_
:
或者你可能破坏了 Promise
。无论哪种方式你的 .then
代码运行,你需要区分这些情况。 .then
代码有一个参数;这就是 Promise
本身。如果你没有将参数命名为 $_
:
my $promise = Promise.new;
$promise.then: {
put do given .status {
when Kept { 'Huzzah!' }
when Broken { 'Darn!' }
}
}
my $start = now;
loop {
$promise.break if now > $start + 5;
last unless $promise.status ~~ Planned;
sleep 1;
}
Promise Junctions
You can use Junction
s to create an über-Promise
. The .allof
method creates a Promise
that is kept if all of its included Promise
s are kept:
你可以使用 Junction
来创建一个超级 Promise
。 .allof
方法创建一个 Promise
,如果它包含的所有 Promise
都变成 kept 状态,则该 Promise 为 kept 状态:
my $all-must-pass = await Promise.allof:
Promise.in(5).then( { put 'Five seconds later' } ),
start { sleep 3; put 'Three seconds later'; },
Promise.at( now + 1 ).then( { put 'One second later' } );
put $all-must-pass;
The .anyof
Promise
is kept if any of its included Promise
s are kept. All except one of them can be broken and the larger Promise
is still kept:
如果 .anyof
Promise
中的任何一个 Promise 是 kept 状态,那么该 Promise 为 kept 状态。除了其中一个之外的所有部分都可以是 broken 状态,而更大的 Promise
仍然是 kept 状态:
my $any-can-pass = await Promise.anyof:
Promise.in(5).then( { put 'Five seconds later' } ),
start { sleep 3; put 'Three seconds later'; fail },
Promise.at( now + 1 ).then( { put 'One second later' } );
put $any-can-pass;
Both of these succeed. In the .allof
case you see the output from all three Promise
s. Then you see the output from one of the Promise
s from .anyof
. Not all of those Promise
s need to finish because the overall Promise
already knows it can succeed:
这两个都成功了。在 .allof
情况下,你可以看到所有三个 Promise
的输出。然后你会看到来自 .anyof
的一个 Promise
的输出。并非所有 Promise
都需要完成,因为整体 Promise
已经知道它可以成功:
One second later
Three seconds later
Five seconds later
True
One second later
True
Reactive Programming
A react
Block
allows you to run some code when new values are available. It keeps running until it runs out of values to handle. It’s similar to an event loop. Here’s a very simple example:
当有新值可用时, react
Block
允许运行一些代码。它一直运行,直到它用完了要处理的值。它类似于事件循环。这是一个非常简单的例子:
react {
whenever True { put 'Got something that was true' }
}
END put "End of the program";
You use whenever
to supply values to the Block
of code. In this case you have the single value True
. This isn’t a conditional expression or a test, as in if
or while
. The Block
reacts to that single value and runs the whenever
code. After that there are no more values and the Block
exits:
你可以使用 whenever
为 Block
块提供值。在这个例子中,你具有单个值 True
。这不是条件表达式或测试,如 if
或 while
。 Block
对该单个值作出反应并运行 whenever
代码。之后没有更多的值, Block
退出:
Got something that was true
End of the program
You might be tempted to think of this as a looping construct, but it’s not quite the same thing. It’s not doing everything in the react
Block
then starting the Block
again. The whenever
for True
only runs once, instead of running forever as you’d expect with a loop
:
你可能会想到这是一个循环结构,但它并不完全相同。它没有在 react
Block
中执行所有操作,然后再次启动Block
。True
的 whenever
只运行一次,而不是像你期望的那样永远运行:
loop {
if True { put 'Got something that was true' }
}
Change the whenever
from True
to a Supply.interval
and you never see the end-of-program message:
将 whenever
从 True
更改为 Supply.interval
,你永远不会看到程序结束消息:
my $supply = Supply.interval: 1;
react {
whenever $supply { put "Got $^a" }
}
END put "End of the program";
As long as the Supply
has values for whenever
, the react
Block
keeps going:
只要 Supply
具有 whenever
可用的值,react
Block
就会继续:
Got 0
Got 1
Got 2
...
You could have both the Supply
and the True
at the same time:
你可以同时拥有 Supply
和 True
:
my $supply = Supply.interval: 1;
react {
whenever $supply { put "Got $^a" }
whenever True { put 'Got something that was true' }
}
END put "End of the program";
The whenever
with the Supply
reacts immediately and outputs the first value in the Supply
. The whenever
with the True
reacts next and exhausts its values (the single True
). After that the Supply
continues until you give up and interrupt the program:
whenever
和 Supply
立即作出反应并输出 Supply
中的第一个值。接下来 whenever
和 True
做出反应并耗尽其值(单个 True
)。之后,Supply
继续,直到你放弃并中断程序:
Got 0
Got something that was true
Got 1
Got 2
...
If you reverse the whenever
s the True
will probably react first:
如果你把两个 whenever
的顺序对调,那么 True
可能会先做出反应:
my $supply = Supply.interval: 1;
react {
whenever True { put 'Got something that was true' }
whenever $supply { put "Got $^a" }
}
END put "End of the program";
The output is slightly different, but there’s nothing that says this has to be the case. Perhaps future implementations will choose differently. This is concurrency; you can’t depend on strict order of happenings:
输出略有不同,但没有任何说法必须如此。未来的实现可能会有不同的选择。这是并发;你不能依赖严格的发生顺序:
Got something that was true
Got 0
Got 1
Got 2
...
Instead of interrupting the program to get the react
to stop, you can do it from within the Block
with done
. You can use a Promise
with .in
to provide a value after some interval:
你可以在 Block
使用 done
中完成,而不是中断程序以使响应停止。你可以使用带有 .in
的 Promise
在一段时间后提供一个值:
my $supply = Supply.interval: 1;
react {
whenever $supply { put "Got $^a" }
whenever True { put 'Got something that was true' }
whenever Promise.in(5) { put 'Timeout!'; done }
}
END put "End of the program";
After five seconds the Promise
is kept and the whenever
kicks in. It outputs the timeout message and uses done
to end the react
:
五秒后, Promise
变为 kept,然后 whenever
执行。它输出超时消息并使用 done
结束 react
:
Got 0
Got something that was true
Got 1
Got 2
Got 3
Got 4
Got 5
Timeout!
End of the program
Add another react
and the process starts over with a fresh Supply
:
添加另一个 react
,然后重新开始一个新的 Promise
:
my $supply = Supply.interval: 1;
react {
whenever $supply { put "Got $^a" }
whenever True { put 'Got something that was true' }
whenever Promise.in(5) { put 'Timeout!'; done }
}
put "React again";
react {
whenever $supply { put "Got $^a" }
}
END put "End of the program";
The output for the Supply
starts again, but at the beginning of the interval:
Promise
的输出再次开始,但在间隔开始时:
Got 0
Got something that was true
Got 1
Got 2
Got 3
Got 4
Timeout!
React again
Got 0
Got 1
EXERCISE 18.4Modify the double react
example to use a live Supply
instead of an on-demand one. How does the output change?
练习18.4修改双反应示例以使用实时供应而不是按需供应。输出如何变化?
Reacting in the Background
The react
is a way that you can respond to values when they are available. So far you’ve seen the react
as a top-level Block
. It keeps running—and holds up the rest of the program—until it’s done.
react
是一种可以在值可用时响应值的方式。到目前为止,你已经看到了作为顶级Block的 react
。它一直运行 - 并持有程序的其余部分 - 直到完成。
Instead, you most likely want your react
to do its work in the background as your program does other things. You can wrap the react
in a Promise
with a start
. That allows the react
to work in a thread as the rest of the program continues:
相反,你最有可能希望你的 react
能够在后台执行其工作,因为你的程序会执行其他操作。你可以用 start
将react
包装在 Promise
中。这允许 react
在程序的其余部分继续时在线程中工作:
my $supply = Supply.interval: 1;
my $promise = start {
react {
whenever $supply { put "Got $^a" }
whenever True { put 'Got something that was true' }
whenever Promise.in(5) { put 'Timeout!'; done }
}
}
put 'After the react loop';
await $promise;
put 'After the await';
END put "End of the program";
The first line of the output is from the put
after the start
block. The react
is starting its work, but it’s not blocking the rest of the program:
输出的第一行来自 start
块之后的 put
。react
开始了它的工作,但它没有阻塞程序的其余部分:
After the react loop
Got 0
Got something that was true
Got 1
Got 2
Got 3
Got 4
Timeout!
After the await
End of the program
Take it up a notch. Add a Channel
into it. Move the Supply
inside the whenever
. When that Supply
has a value it executes the Block
to output the same thing it did before. It also sends the value to the Channel
if it is a multiple of 2.
把它提升一个档次。添加一个 Channel
。把 Supply
移动到 whenever
里。当该 Supply
有一个值时,它执行 Block
以输出它之前执行的相同操作。如果它是 2 的倍数,它还会将值发送到 Channel
。
Add a second whenever
to read the values available on the Channel
. You need to convert the Channel
to a Supply
; that’s easy because there’s a .Supply
method. The whenever
taps that Supply
:
添加第二个 whenever
以读取 Channel
上可用的值。你需要将 Channel
转换为 Supply
; 这很简单,因为有一个 .Supply
方法。whenever
点击 Supply
:
my $channel = Channel.new;
my $promise = start {
react {
whenever Supply.interval: 1
{ put "Got $^a"; $channel.send: $^a if $^a %% 2 }
whenever $channel.Supply
{ put "Channel got $^a" }
whenever True
{ put 'Got something that was true' }
whenever Promise.in(5)
{ put 'Timeout!'; done }
}
}
put 'After the react loop';
await $promise;
put 'After the await';
END put "End of the program";
The output is mostly the same as before with the Channel
output inserted:
插入 Channel
输出时输出与以前大致相同:
After the react loop
Got 0
Got something that was true
Channel got 0
Got 1
Got 2
Channel got 2
Got 3
Got 4
Channel got 4
Timeout!
After the await
End of the program
EXERCISE 18.5Use IO::Notification
to output a message every time there’s a change to a file you specify on the command line.
练习18.5使用 IO::Notification
在每次对命令行指定的文件进行更改时输出消息。
Summary
Promise
s are the basis of concurrency, and there are various ways that you can create them to get what you what. Decompose your problem into independent bits and run them as Promise
s, which can run in separate threads (or maybe even on different cores). With those, Supply
s and Channel
s provide a way to pass data between disconnected parts of your program. To get the most out of all of these you need to think differently from the procedural stuff you’ve seen so far. You’ll get that with practice.
Promise
是并发的基础,有多种方法可以创建它们来获得你的东西。将你的问题分解为独立的部分并将它们作为Promise
运行,它可以在不同的线程中运行(甚至可以在不同的核心上运行)。有了这些,Supply
和 Channel
提供了一种在程序的断开连接部分之间传递数据的方法。为了充分利用所有这些,你需要与迄今为止看到的程序性内容进行不同的思考。通过练习你会掌握它。