我想逐步编写示例代码,如何分离生成器中的任务并将它们移动到2个或更多生成器中,以实现它们之间的协作式多任务处理。您可以找到关于此here的所有测试。
生成器在某种程度上是合乎逻辑的,但是我被一步所困,我无法解释为什么它以这种方式工作:
发电机:
$spy = new Object();
$spy->tasks = array();
$createGenerator = function ($i1) use ($spy) {
yield; //(* -> task 1)
$spy->tasks[] = $i1;
yield($i1); //(task 1 -> *)
$i1 = yield; //(* -> task 2)
//task 2
$i2 = $i1 + 1;
$spy->tasks[] = $i2;
yield($i2); //(task 2 -> *)
$i2 = yield; //(* -> task 3)
$i3 = $i2 + 1;
$spy->tasks[] = $i3;
yield($i3); //(task 3 -> *)
$i3 = yield; //(* -> task 4)
$i4 = $i3 + 1;
$spy->tasks[] = $i4;
yield($i4); //(task 4 -> *)
$i4 = yield; //(* -> task 5)
$i5 = $i4 + 1;
$spy->tasks[] = $i5;
yield($i5); //(task 5 -> *)
};
我等待成功的测试,但失败了:
/** @var Generator $generator */
$generator = $createGenerator(1);
$i1 = $generator->send(null);
$generator->send($i1);
$i2 = $generator->send(null);
$generator->send($i2);
$i3 = $generator->send(null);
$generator->send($i3);
$i4 = $generator->send(null);
$generator->send($i4);
$i5 = $generator->send(null);
$this->assertSame($spy->tasks, array(1, 2, 3, 4, 5));
$this->assertSame(array($i1, $i2, $i3, $i4, $i5), array(1, 2, 3, 4, 5));
意外成功的测试:
/** @var Generator $generator */
$generator = $createGenerator(1);
$i1 = $generator->send(null);
$generator->send(null); //blank sends needed to skip the yield-yield gaps
$i2 = $generator->send($i1);
$generator->send(null);
$i3 = $generator->send($i2);
$generator->send(null);
$i4 = $generator->send($i3);
$generator->send(null);
$i5 = $generator->send($i4);
$this->assertSame($spy->tasks, array(1, 2, 3, 4, 5));
$this->assertSame(array($i1, $i2, $i3, $i4, $i5), array(1, 2, 3, 4, 5));
你能用双yield
向我解释生成器的奇怪行为吗?
结论:
send()
始终将代码从yield
的输入运行到下一个yield
的输出。因此,通过使用Generator
运行send()
,它始终以输入开头,这就是为什么您无法使用yield
获取第一个send()
的输出,这就是为什么在null
进入无效状态之前,你总是在send()
之前获得Generator
返回值的原因...遗憾的是PHP手册缺少这个信息......
答案 0 :(得分:1)
测试生成器的工作示例:
$spy = new stdClass();
$spy->tasks = array();
$createGenerator = function ($i1) use ($spy) {
yield;
$spy->tasks[] = $i1;
$i1 = (yield $i1);
yield;
$i2 = $i1 + 1;
$spy->tasks[] = $i2;
$i2 = (yield $i2);
yield;
$i3 = $i2 + 1;
$spy->tasks[] = $i3;
$i3 = (yield $i3);
yield;
$i4 = $i3 + 1;
$spy->tasks[] = $i4;
$i4 = (yield $i4);
yield;
$i5 = $i4 + 1;
$spy->tasks[] = $i5;
(yield $i5);
};
你的考试:
$generator = $createGenerator(1);
$i1 = $generator->send(null);
$generator->send($i1);
$i2 = $generator->send(null);
$generator->send($i2);
$i3 = $generator->send(null);
$generator->send($i3);
$i4 = $generator->send(null);
$generator->send($i4);
$i5 = $generator->send(null);
print_r($spy);
for ($i = 1; $i <= 5; ++$i) {
echo ${'i'.$i} . "\n";
}
这给出了期望的结果:
stdClass Object
(
[tasks] => Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
)
)
1
2
3
4
5
有关详细信息,请参阅the manual on the send method,其中总结了send
的工作原理:
将当前值作为当前结果发送给生成器 yield表达式并恢复生成器的执行。
如果此方法为,则生成器不在yield表达式中 调用时,首先要让它前进到第一个yield表达式 在发送价值之前。
您应该已经知道yield
做了什么。
要完全理解生成器和测试之间的交互,它应该可以帮助您在源代码的执行流程中的每一步(用笔在一张纸上写下来)。
另请注意yield in the manual上的警告框:
注意
如果在表达式上下文中使用yield(例如,在 在任务的右侧),你必须包围产量 带圆括号的陈述。例如,这是有效的:
$ data =(yield $ value);
但这不是,并且会导致解析错误:
$ data = yield $ value;
此语法可与结合使用 Generator :: send()方法。
答案 1 :(得分:1)
这使我困惑了几个小时,但是我设法弄清了到底是什么以及send
的工作方式。重要结论是:
send
还是从中读取(通过foreach
或current()
),它将运行到第一个{{1 }}进行“初始化” ,然后在该yield
上执行发送/接收操作。如果您的第一个操作正在调用yield
,它将一直运行到第二个next()
。yield
依次执行发送和读取操作,无论您对结果如何处理。这意味着当您呼叫yield
时,生成器将在处理您的呼叫时移至下一个send()
。考虑到这一点,让我们逐步进行测试:
send()
仅使生成器产生,并且不运行任何操作。yield
首先执行发送,然后执行读取:
$createGenerator(1)
。$i1 = $generator->send(null);
就是这个yield
的值。null
,将yield
添加到您的任务列表中,然后以产生的值yield
将执行交还给您。$i1
。1
现在将此值发送回Generator,再次执行发送,然后将Generator推进到下一个$i1
,并读取产生的值:
$generator->send($i1);
位置,该位置以前提供了值yield
。这是出口,现在成为接收发送值的入口(上面的第二个结论)。yield($i1);
。1
不提供值,所以产生yield
。yield
被丢弃而不是存储。 li>
null
现在发送1
,再次导致发送和读取操作按该顺序进行,从而在此过程中推进了Generator的运行:
$i2 = $generator->send(null);
是当前的null
,以前是出口点,现在变成了入口点。 由于我们刚刚发送了$i1 = yield; //(* -> task 2)
,因此存储在yield
中。null
(所以$i1
)存储在null + 1
中,并将其存储在任务列表中,然后产生它。1
现在存储在测试代码的$i2
中。此时,先前的错误已泄漏到测试代码中。1
,并由发送它的$i2
语句丢弃,然后是下一个{{1} }将存储您接下来发送的1
并重复所有操作。您得到的数组将是yield
。您的工作测试之所以有效,是因为它将发送的值进一步提高了一步,到达了yield
,它实际上存储了您发送的内容。 @GhostGambler的Generator的工作原理相反,它将Generator内的赋值移近了null
,后者实际上从测试代码中接收了值。
为说明这些内部工作原理,请考虑以下改编示例:
[1, 1, 1, 1, 1]
将创建以下输出:
yield
请注意,永远不会捕获“ out1”(第一个产生的值),因为对Generator的第一个操作是发送。因此,最后一次读取操作失败,因为Generator仅产生4个值,并且我们试图访问第五个产生的值(因为我们隐式丢弃了第一个)。
最后一个陷阱是yield
,它在$createGenerator = function ($i) {
$in1 = yield("out".$i++); //(* -> task 1)
echo "in1: $in1";
$in2 = yield("out".$i++); //(task 1 -> *)
echo "in2: $in2";
$in3 = yield("out".$i++); //(* -> task 2)
echo "in3: $in3";
$in4 = yield("out".$i++); //(task 2 -> *)
echo "in4: $in4";
echo "\nGenerator done with i=$i";
};
$generator = $createGenerator(1);
$out = $generator->send("in1");
echo "\nout1: ";var_dump($out);
$out = $generator->send("in2");
echo "\nout2: ";var_dump($out);
$out = $generator->send("in3");
echo "\nout3: ";var_dump($out);
$out = $generator->send("in4");
echo "\nout4: ";var_dump($out);
循环的每次迭代结束时都会隐式调用。该变量实际上与in1: in1
out1: string(4) "out2"
in2: in2
out2: string(4) "out3"
in3: in3
out3: string(4) "out4"
in4: in4
Generator done with i=5
out4: NULL
相同:它不向Generator发送任何内容,使其运行到下一个next()
,并丢弃下一个产生的值(尽管在foreach
循环中,{{然后还调用1}},以提取该丢弃的值并将其存储在send(null)
变量中)。这使得在yield
中使用foreach
相当棘手。