9

请考虑下面的代码。为什么这个输出是“BABAA”而不是“AABAA”/“AABAAAB”?这两个供应不应该并行运行,并且当其中任何一个发生事件时立即触发?

my $i = 0; 
my $supply1 = supply { loop { await Promise.in(3); done if $i++> 5; emit("B"); } };
my $supply2 = supply { loop { await Promise.in(1); done if $i++> 5; emit("A"); } };

react 
{ 
    #whenever Supply.merge($supply1, $supply2) -> $x { $x.print }
    whenever $supply1 -> $x { $x.print };
    whenever $supply2 -> $x { $x.print };
}
4

4 回答 4

8

当我们订阅一个supply块时,该块的主体会supply立即运行以设置订阅。其中没有引入并发性;如果我们想要,我们需要提出要求。

最佳解决方案取决于示例与您正在做的事情的接近程度。如果它非常接近 - 并且您想要emit每个时间间隔的值 - 那么解决方案是使用Supply.interval

my $i = 0; 
my $supply1 = supply { whenever Supply.interval(3, 3) { done if $i++ > 5; emit("B"); } };
my $supply2 = supply { whenever Supply.interval(1, 1) { done if $i++> 5; emit("A"); } };

react { 
    whenever $supply1 -> $x { $x.print };
    whenever $supply2 -> $x { $x.print };
}

它只是设置订阅并退出设置,因此提供您想要的输出,但是您确实在$i.

更一般的模式是只做任何让循环发生在设置步骤之外的事情。例如,我们可以使用 a keepPromise来“重击”它:

my constant READY = Promise.kept;
my $i = 0;
my $supply1 = supply whenever READY {
    loop { await Promise.in(3); done if $i++> 5; emit("B"); }
}
my $supply2 = supply whenever READY {
    loop { await Promise.in(1); done if $i++> 5; emit("A"); }
}

react { 
    whenever $supply1 -> $x { $x.print };
    whenever $supply2 -> $x { $x.print };
}

这很有帮助,因为 a 的结果Promise将通过线程池调度程序传递给supply块,从而强制将whenever包含循环的内容执行到它自己的计划任务中。

这不是特别漂亮,但是如果我们定义一个函数来做到这一点:

sub asynchronize(Supply $s) {
    supply whenever Promise.kept {
        whenever $s { .emit }
    }
}

那么原程序只需要添加两个调用即可:

my $i = 0;
my $supply1 = supply { loop { await Promise.in(3); done if $i++> 5; emit("B") } }
my $supply2 = supply { loop { await Promise.in(1); done if $i++> 5; emit("A") } }

react { 
    whenever asynchronize $supply1 -> $x { $x.print }
    whenever asynchronize $supply2 -> $x { $x.print }
}

使其按需要工作。可以说,这样的东西应该作为内置提供。

正如其他解决方案所建议的那样,也可以使用 a Channelas well ,并且取决于手头可能合适的问题;这个问题对我来说有点太抽象了。这个解决方案保持在Supply范式内,并且在这个意义上更整洁。

于 2019-08-15T17:59:34.673 回答
4

感谢这里的jjmerelo,我设法让它工作。渠道是正确的轨道,但您实际上必须消耗渠道供应。

use v6;

my Channel $c .= new;
my $supply1 = start { loop { await Promise.in(1); $c.send("B"); } };
my $supply2 = start { loop { await Promise.in(0.5); $c.send("A"); } };

react 
{ 
    whenever $c.Supply -> $x { $x.print };
}

$c.close;

附加问题:这个规模有多好?你能有几千个供应品发送到渠道吗?

于 2019-08-15T01:28:25.023 回答
3

供应是异步的,不是并发的。您将需要使用渠道而不是耗材来同时喂养它们。

use v6;

my $i = 0;
my Channel $c .= new;
my $supply1 = start { for ^5 { await Promise.in(1); $c.send("B"); } };
my $supply2 = start { for ^5 { await Promise.in(0.5); $c.send("A"); } };

await $supply2;
await $supply1;
$c.close;

.say for $c.list;

在这种情况下,两个线程同时启动,而不是使用.emit,然后.send到通道。在您的示例中,它们在等待时被有效地阻塞,因为它们都在同一个线程中运行。它们仅在遵守承诺后才将控制权交给其他供应,因此它们显然是“并行”运行的,并且与它们中较慢的一样慢。

于 2019-08-14T08:21:14.910 回答
2

好的,这是我的真实代码。它似乎有效,但我认为某处存在竞争条件。这是一些典型的(尽管很短)输出。

A monster hatched.
A monster hatched.
A hero was born.
The Monster is at 2,3
The Monster is at 3,2
The Player is at 0,0
The Monster (2) attacks the Player (3)
The Monster rolls 14
The Player rolls 4
The Monster inflicts 4 damage
The Player (3) attacks the Monster (2)
The Player rolls 11
The Monster rolls 8
The Player inflicts 45 damage
The Monster is dead
The Monster is at -3,-3
The Player is at 4,-3
The Monster (1) attacks the Player (3)
The Monster rolls 8
The Player rolls 5
The Monster inflicts 11 damage
The Player has 32 hitpoints left
The Monster is at -4,1
The Player is at -1,4
The Player (3) attacks the Monster (1)
The Player rolls 12
The Monster rolls 11
The Player inflicts 46 damage
The Monster is dead
Stopping
Game over. The Player has won

现在奇怪的是,有时,在 20% 的运行中,输出的最后一行是

Game over. The GameObject has won 

好像物体已经部分解构时被抓住了?或者其他的东西?无论如何,这是代码。

class GameObject
{
    has Int $.id;
    has Int $.x is rw;
    has Int $.y is rw;
    has $.game;
    has Int $.speed; #the higher the faster
    has Bool $.stopped is rw;

    multi method start( &action )
    {
        start {
            loop {
                &action();
                last if self.stopped;
                await Promise.in( 1 / self.speed );
            }
            $.game.remove-object( self );
        }
    }

    method speed {
        $!speed + 
            # 33% variation from the base speed in either direction
            ( -($!speed / 3).Int .. ($!speed / 3).Int ).pick
            ;
    }
}

role UnnecessaryViolence
{
    has $.damage;
    has $.hitpoints is rw;
    has $.offense;
    has $.defense;

    method attack ( GameObject $target )
    {
        say "The {self.WHAT.perl} ({self.id}) attacks the {$target.WHAT.perl} ({$target.id})";

        my $attacker = roll( $.offense, 1 .. 6 ).sum;
        say "The {self.WHAT.perl} rolls $attacker";

        my $defender = roll( $target.defense, 1 .. 6 ).sum;
        say "The {$target.WHAT.perl} rolls $defender";

        if $attacker > $defender 
        {
            my $damage = ( 1 .. $.damage ).pick;
            say "The {self.WHAT.perl} inflicts {$damage} damage";

            $target.hitpoints -= $damage ;
        }

        if $target.hitpoints < 0
        {
            say "The {$target.WHAT.perl} is dead";
            $target.stopped = True;
        }
        else
        {
            say "The {$target.WHAT.perl} has { $target.hitpoints } hitpoints left";
        }
    }
}

class Player is GameObject does UnnecessaryViolence
{
    has $.name;

    multi method start
    {
        say "A hero was born.";
        self.start({
            # say "The hero is moving";
            # keyboard logic here, in the meantime random movement
            $.game.channel.send( { object => self, x => (-1 .. 1).pick, y => (-1 .. 1).pick } );
        });
    }
}

class Monster is GameObject does UnnecessaryViolence
{
    has $.species;

    multi method start
    {
        say "A monster hatched.";
        self.start({
            # say "The monster {self.id} is moving";
            # AI logic here, in the meantime random movement
            $.game.channel.send( { object => self, x => (-1 .. 1).pick, y => (-1 .. 1).pick } );
        });
    }
}

class Game
{
    my $idc = 0;

    has GameObject @.objects is rw;
    has Channel $.channel = .new;

    method run{
        self.setup;
        self.mainloop;
    }

    method setup
    {
        self.add-object( Monster.new( :id(++$idc), :species("Troll"), :hitpoints(20), :damage(14), :offense(3), :speed(300), :defense(3), :x(3), :y(2), :game(self) ) );
        self.add-object( Monster.new( :id(++$idc), :species("Troll"), :hitpoints(10), :damage(16), :offense(3), :speed(400), :defense(3), :x(3), :y(2), :game(self) ) );
        self.add-object( Player.new( :id(++$idc), :name("Holli"), :hitpoints(50), :damage(60), :offense(3), :speed(200) :defense(2), :x(0), :y(0), :game(self) ) );
    }

    method add-object( GameObject $object )
    {
        @!objects.push( $object );
        $object.start;
    }

    method remove-object( GameObject $object )
    {
        @!objects = @!objects.grep({ !($_ === $object) });
    }

    method mainloop 
    { 
        react {
            whenever $.channel.Supply -> $event
            {
                self.stop-game
                    if self.all-objects-stopped;

                self.process-movement( $event );

                self.stop-objects
                  if self.game-is-over;

            };
            whenever Supply.interval(1) {
                self.render;
            }
        }

    }

    method process-movement( $event )
    {
        #say "The {$event<object>.WHAT.perl} moves.";
        given $event<object>
        {
            my $to-x = .x + $event<x>;
            my $to-y = .y + $event<y>;

            for @!objects -> $object
            {
                # we don't care abour ourselves
                next 
                    if $_ === $object;

                # see if anything is where we want to be
                if ( $to-x == $object.x && $to-y == $object.y )
                {
                    # can't move, blocked by friendly
                    return 
                        if $object.WHAT eqv .WHAT;

                    # we found a monster
                    .attack( $object );
                    last;
                }
            }

            # -5 -1 5 
            # we won the fight or the place is empty
            # so let's move
            .x = $to-x ==  5  ?? -4 !!
                 $to-x == -5  ?? 4  !!
                 $to-x;

            .y = $to-y ==  5  ?? -4 !!
                 $to-y == -5  ?? 4  !!
                 $to-y;

        }
    }

    method render
    {
        for @!objects -> $object {
            "The {$object.WHAT.perl} is at {$object.x},{$object.y}".say;
        }
    }

    method stop-objects
    {
        say "Stopping";
        for @!objects -> $object {
            $object.stopped = True;
        }
    }

    method stop-game {
        "Game over. The {@!objects[0].WHAT.perl} has won".say;
        $.channel.close;
        done;
    }

    method game-is-over {
        return (@!objects.map({.WHAT})).unique.elems == 1;
    }

    method all-objects-stopped {
        (@!objects.grep({!.stopped})).elems == 0;
    }



}

Game.new.run;
于 2019-08-16T02:37:15.077 回答