检查反应块是否准备好用于业务

Checking if a react block is ready for business

编写并发代码时,很常见的情况是要分离一个单独的(绿色或 OS)线程,然后要求该线程中的代码对各种线程安全消息作出反应。 Raku 以多种方式支持这种模式。

例如,文档中的许多 Channel examples 显示的代码类似于下面的代码(跨两个线程打印 1 到 10)。

my $channel = Channel.new;
start   { react whenever $channel { say $_ }}
for ^10 { $channel.send($_) }
sleep 1

但是,如果我们从 Channels 的单消费者世界切换到实时 Supplys 的多消费者世界,等效代码将不再有效。

my Supplier $supplier .= new;
start   { react whenever $supplier { say $_ }}
for ^10 { $supplier.emit($_) }
sleep 1;

此代码不打印任何内容。据我了解,这是因为 react 块在值被 emited 时没有监听 - start 线程和 react 不需要很长时间事件,但 emit 十个值花费的时间更少。而且,从逻辑上讲,将 sleep 1 行移动到 for 循环上方会导致再次打印值。

这很公平——毕竟,使用实时 Supply 而不是点播的原因是因为您需要实时语义。也就是说,您只想 react 到未来的事件,而不是过去的事件。

但我的问题是是否有办法在线程中询问 react 块我已经 start 编辑它是否准备好 and/or 等待它之前准备好发送数据。 (awaiting start 块会等到线程 完成 ,而不是等到它准备好,所以这对这里没有帮助)。

我也乐于接受这样的回答,说我正在处理这个 incorrectly/there 的 X-Y 问题——我完全有可能违背语言试图推动我或那个的方向此处实时 Supply 不是正确的并发抽象。

对于这种特定情况(这是一个相对常见的情况),答案是使用 Supplier::Preserving:

my Supplier::Preserving $supplier .= new;
start   { react whenever $supplier { say $_ }}
for ^10 { $supplier.emit($_) }
sleep 1;

它保留发送的值直到 $supplier 被第一次点击,然后发出它们。

另一种更通用的解决方案是使用 Promise:

my Supplier $supplier .= new;

# A Promise used just for synchronization
my Promise $ready .= new;

start react {
    # Set up the subscriptions...
    whenever $supplier { say $_ }
    # ...and then signal that they are ready.
    $ready.keep;
}

# Wait for the subscriptions to be set up...
await $ready;

# ...and off we go.
for ^10 { $supplier.emit($_) }
sleep 1;

react 块中的 whenever 在遇到时设置订阅,因此到 Promise 保留时,所有订阅都已完成。 (此外,虽然这里不重要,但在 react 块的主体完成所有设置之前不会处理任何消息。)

最后我会注意到,虽然 Supplier 经常被达到,但很多时候最好写一个 supply 块,其中 emit 是值。问题中的示例(相当合理)是从具体应用程序中抽象出来的,但几乎总是值得一问,“我可以通过编写 supply 块来做我想做的事情吗?”然后再达到 SupplierSupplier::Preserving。如果您确实需要广播值或需要将异步输入分发到多个位置,那么 Supplier 是个不错的选择;如果它只是一个单一的价值流,一旦被点击就会产生,那么可能没有。