在编写并发代码时,想要剥离一个单独的(绿色或操作系统)线程,然后要求该线程中的代码对各种线程安全消息做出反应是相当常见的。 Raku 以多种方式支持这种模式。
例如,文档中的许多 Channel 示例显示的代码类似于下面的代码(跨两个线程打印 1 到 10)。
my $channel = Channel.new;
start { react whenever $channel { say $_ }}
for ^10 { $channel.send($_) }
sleep 1
但是,如果我们从Channel
的单消费者世界切换到实时Supply
的多消费者世界,则等效的代码将不再有效。
my Supplier $supplier .= new;
start { react whenever $supplier { say $_ }}
for ^10 { $supplier.emit($_) }
sleep 1;
此代码不打印任何内容。 据我了解,这是因为react
块在emit
值时没有侦听 -start
线程并react
事件不需要很长时间,但emit
十个值所需的时间更少。 而且,从逻辑上讲,将sleep 1
行移动到for
循环上方会导致再次打印值。
这一切都很公平——毕竟,使用实时Supply
而不是按需的原因是因为您想要实时语义。 也就是说,您只想react
未来的事件,而不是过去的事件。
但我的问题是,是否有办法在我start
的线程中询问react
块是否准备就绪和/或在发送数据之前等待它准备就绪。 (await
start
块会等到线程完成,而不是等到它准备好,所以这在这里没有帮助)。
我也愿意接受答案,说我正在错误地接近这个/存在 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
值。问题中的示例(相当合理)是从具体应用程序中抽象出来的,但在获得Supplier
或Supplier::Preserving
之前,几乎总是值得问"我可以通过编写一个supply
块来做我想做的事吗"。如果您确实需要广播值或需要将异步输入分配到多个位置,那么Supplier
有一个坚实的理由;如果它只是在点击后产生的单个值流,那么可能没有。