共享Supply是否可同时运行多个tap块?附代码验证分析
答案是可以,但这里有个关键细节需要理清:共享Supply的作用是让多个tap订阅同一份事件流(每个事件会分发给所有订阅的tap),但默认情况下,tap的回调处理是同步执行的——如果某个tap的逻辑耗时很长,会阻塞其他tap的处理,甚至导致部分事件来不及处理就随程序结束而中断。
我们结合你给出的例子来拆解:
非共享Supply的情况
先看你提供的第一个代码片段:
my $supply = Supply.interval(0.2); my $tap = $supply.tap: { say "1 $^a"; sleep 5; }; sleep 5;
这里Supply.interval(0.2)会每隔0.2秒生成一个事件,但因为没有调用.share,这个Supply是“冷”的——只有当有tap订阅时才开始生成事件。不过你这里只有一个tap,所以事件会依次发给它。但因为tap里的sleep 5是阻塞操作,程序主流程的sleep 5结束后就会退出,而tap里的回调还在sleep,不过实际输出有25行(对应5秒内的所有事件),这是因为interval生成的事件会被暂存,当回调的sleep结束后,会快速处理完积压的所有事件,直到程序终止。
共享Supply的情况
当你把Supply改成.share后,它变成了“热”Supply——不管有没有tap订阅,事件都会持续生成(或者说,新的tap会从当前时间点开始接收后续事件)。但默认的tap回调还是同步执行的,比如如果我们加第二个tap:
my $supply = Supply.interval(0.2).share; my $tap1 = $supply.tap: { say "Tap 1: $^a"; sleep 5; }; my $tap2 = $supply.tap: { say "Tap 2: $^a"; }; sleep 5;
此时,每个事件会先触发tap1的回调,等它的sleep 5完成后,才会触发tap2的回调。这就导致tap2的输出会严重滞后,而且程序主流程的sleep 5结束后,大部分事件的处理还没完成,会随程序终止而中断。
让多个tap同时运行的正确姿势
如果想让多个tap的回调同时异步执行,不需要阻塞彼此,你可以把tap里的耗时逻辑放到start块里,启动一个异步任务:
my $supply = Supply.interval(0.2).share; my $tap1 = $supply.tap: { start { say "Tap 1: $^a"; sleep 5; } }; my $tap2 = $supply.tap: { say "Tap 2: $^a"; }; sleep 6; # 多等1秒,确保tap1的异步任务能完成
这样,tap1的回调会立即启动一个异步线程去处理sleep逻辑,不会阻塞tap2的回调执行,两个tap就能同时处理每个事件了。
总结一下:共享Supply本身支持多个tap订阅,但要让tap的处理同时运行,需要手动将耗时逻辑放到异步上下文里,避免阻塞事件流的分发和其他tap的执行。
内容的提问来源于stack exchange,提问作者brian d foy

