perl6 "An operation first awaited"

perl6 "An operation first awaited"

此程序创建一个线程以使用 dir() 读取目录并将文件放置在通道上。 $N 个工作线程读取该通道并 "process"(打印)文件。

但是我收到这个 "An operation first awaited:" 错误。

我已经多次阅读有关此错误的陷阱页面,但仍然无法理解。有人能解释一下这是怎么回事吗?

目录内容:

$ ls
a  b  c  traverse-dir0.p6

运行 程序:

$ ./traverse-dir0.p6 
traverse-dir0.p6
a
b
c
An operation first awaited:
  in sub MAIN at ./traverse-dir0.p6 line 24
  in block  at ./traverse-dir0.p6 line 5

Died with the exception:
    Cannot find method 'path': no method cache and no .^find_method
      in block  at ./traverse-dir0.p6 line 16

程序traverse-dir0.p6:

#!/usr/bin/env perl6
# There is a thread to populate $dir-channel by reading filenames in a directory with dir()
# and $N worker threads to read the filenames from the $dir-channel.

sub MAIN( Str $dir = ".", Int :$N = 4 ) {

    my $dir-channel = Channel.new();
    my $dir-read = start {
        $dir-channel.send( $_ ) for dir $dir;
        $dir-channel.close;
    }

    my @workers = (^$N).map: {
        start {
            while my $file = $dir-channel.receive() {
                say $file.path;
            }
            CATCH {
                when X::Channel::ReceiveOnClosed { .resume }
            }
        }
    }

    await $dir-read, @workers;
}

首先,关于 await 抛出异常的输出。异步操作失败时有两条有趣的信息:

  • 在程序中我们想要操作结果的地方
  • 程序中哪里出现问题导致无法执行操作

第一条信息指示 await 的位置,堆栈跟踪与此相关。第二部分是关于await异常被重新抛出的原因,并指出需要修复的问题。

本例中的问题是 path 方法是在一个没有对象的对象上调用的。这要归功于 .resume,这毫无意义。抛出异常表明无法从通道接收值。恢复它只是意味着循环体在 $file 中以未定义的值运行,它缺少 path 方法并导致错误。 (顺便说一句:.resume 是非常非常罕见的正确答案。)

代码的最小修复是用 last 替换 .resume,当通道关闭时终止迭代:

my @workers = (^$N).map: {
    start {
        while my $file = $dir-channel.receive() {
            say $file.path;
            CATCH {
                when X::Channel::ReceiveOnClosed { last }
            }
        }
    }
}

然而,将 Channel 强制转换为可迭代的 Seq 要简单得多。这会在 Channel 关闭时自动处理终止迭代,因此不会出现异常:

my @workers = (^$N).map: {
    start {
        for $dir-channel.Seq -> $file {
            say $file.path;
        }
    }
}

并且由于 start 是语句前缀,进一步缩短为:

my @workers = (^$N).map: {
    start for $dir-channel.Seq -> $file {
        say $file.path;
    }   
}   

我很欣赏这可能是一个更有趣的问题的简化版本,或者可能是为了探索各种 Perl 6 并发概念而完成的,但所有内容都可以替换为:

sub MAIN( Str $dir = ".", Int :$N = 4 ) {
    race for dir($dir).race(batch => 1, degree => $N) -> $file {
        say $file.path;
    }
}

它具有相同的语义,但节省了启动和管理工作人员的时间,同时仍然控制工作人员的数量并确保文件以相同的方式在工作人员之间分发。