由於send方法是實現協程的關鍵所在,覺得有必要單獨詳細學習下send方法,因為確實詭異; 官方文檔 Generator::send (PHP 5 >= 5.5.0, PHP 7) Generator::send — Send a value to the generator Description public mixed Generator::send ( mixed $value )
Sends the given value to the generator as the result of the current yield expression and resumes execution of the generator.
這裡官方定義:send方法,發送做一個值作為產生器當前執行到的yield運算式的結果值(send方法並不執行當前yield運算式而僅僅是將發送的值作為當前yield運算式的結果值);並且繼續執行產生器(繼續執行下一次yield);
這裡需要特別注意的是,send發送的值作為當前執行到的yield運算式的結果值;send返回的值,是下一個yield運算式的結果值;
If the generator is not at a yield expression when this method is called, it will first be let to advance to the first yield expression before sending the value. As such it is not necessary to “prime” PHP generators with a Generator::next() call (like it is done in Python).
如果當前產生器不在yield運算式,它會先自動運行到第一個yield運算式,然後再執行發送$value的操作;因此,沒有必要使用 Generator::next() 來調用PHP產生器(就像在Python中完成的那樣)。
這裡需要特別注意的是,在第一次調用send方法,它會讓產生器自動運行到第一個yield運算式的位置並且執行第一個yield,而後再執行send方法的調用;這裡在下面有例子說明; Parameters value
Value to send into the generator. This value will be the return value of the yield expression the generator is currently at. Return Values
Returns the yielded value.
這個表示返回的值為執行過的yield運算式的值,但是在運行一次send方法之後,會有可能跨度兩個yield運算式,所以這裡的定義就模糊不明; EXP1
function task(){ echo "This is segment 1\n"; // 這裡是第一程式段,在第一個yield之前; $a = yield 'step 1'; echo "Get step 1 send value : $a\n"; echo "This is segment 2\n"; // 這裡是第二程式段,在第一個yield之後,在第二個yield之前; $b = yield 'step 2'; echo "Get step 2 send value : $b\n"; echo "This is segment 3\n"; // 這裡是第三程式段,在第二個yield之後;}$task = task();$sendResult = $task->send('first send');echo "The first send result : $sendResult\n";/** * 輸出: * This is segment 1 * Get step 1 send value : first send * This is segment 2 * The first send result : step 2 */
根據上例得知:
send方法發送的值 first send 被作為第一個 yield (step 1) 的結果值;
send方法的傳回值是第二個 yield (step 2) 運算式的值;
有疑問:
看到鳥哥的文章裡面說明,當一個產生器方法被調用的時候,rewind方法會自動執行;而PHP官網裡面對remind方法的描述就是簡簡單單的 Generator::rewind — Rewind the iterator,那麼在rewind方法調用的時候,產生器會不會自動執行到第一個yield運算式的位置呢。我們看下個例子,單單將產生器函數賦值給變數; EXP2
function task(){ echo "This is segment 1\n"; // 這裡是第一程式段,在第一個yield之前; $a = yield 'step 1'; echo "Get step 1 send value : $a\n"; echo "This is segment 2\n"; // 這裡是第二程式段,在第一個yield之後,在第二個yield之前; $b = yield 'step 2'; echo "Get step 2 send value : $b\n"; echo "This is segment 3\n"; // 這裡是第三程式段,在第二個yield之後;}$task = task();/** * 輸出: */
輸出為空白,可以得知,將產生器函數賦值的時候,產生器裡面的代碼並沒有執行;那就是說,EXP1例子中,代碼的執行,均由send方法造成的; EXP3
官網對send方法的定義中:
send方法,發送做一個值作為產生器當前執行到的yield運算式的結果值;並且繼續執行產生器(繼續執行產生器,應該更準確的說明:繼續執行下一次yield);
function task(){ echo "This is segment 1\n"; // 這裡是第一程式段,在第一個yield之前; $a = yield 'step 1'; echo "Get step 1 send value : $a\n"; echo "This is segment 2\n"; // 這裡是第二程式段,在第一個yield之後,在第二個yield之前; $b = yield 'step 2'; echo "Get step 2 send value : $b\n"; echo "This is segment 3\n"; // 這裡是第三程式段,在第二個yield之後;}$task = task();$sendResult = $task->send('first send');echo "The first send result : $sendResult\n";$sendResult = $task->send('second send');echo "The second send result : $sendResult\n";/** * 輸出: * This is segment 1 * Get step 1 send value : first send * This is segment 2 * The first send result : step 2 * Get step 2 send value : second send * This is segment 3 * The second send result : */
這個例子中,第一個send方法的傳回值是第二個 yield 運算式的結果值;第二個send方法發送的值成為了第二個 yield 運算式的結果值;兩個send方法執行的過程中,都對第二個 yield 運算式有操作;那麼到底誰才真正執行了第二個 yield 運算式呢。按照官網的說法,應該是第一個send方法執行了第二個 yield 運算式,因為第一個send方法的結果值都是第二個 yield 表達之的結果值,那麼也就是說第二個send方法,僅僅是將發送的參數作為第二個 yield 運算式的結果值,並沒有執行第二個 yield 運算式;如果這樣的話,那麼第一個 yield 運算式是不是就沒有執行,因為如果第二個send方法不執行第二個 yield 運算式的話,那麼也就是說第一個send方法也沒有執行第一個 yield 運算式,那麼第一個 yield 運算式的就真的沒有執行嗎。 EXP4
function task(){ echo "This is segment 1\n"; // 這裡是第一程式段,在第一個yield之前; $a = yield son_task(1); echo "Get step 1 send value : $a\n"; echo "This is segment 2\n"; // 這裡是第二程式段,在第一個yield之後,在第二個yield之前; $b = yield son_task(2); echo "Get step 2 send value : $b\n"; echo "This is segment 3\n"; // 這裡是第三程式段,在第二個yield之後,在第三個yield之前; $c = yield son_task(3); echo "Get step 3 send value : $c\n"; echo "This is segment 4\n"; // 這裡是第四程式段,在第三個yield之後;}function son_task($step){ echo "This is son_task of step $step\n"; return "step $step";}$task = task();$sendResult = $task->send('first send');echo "The first send result : $sendResult\n";/** * 輸出: * This is segment 1 * This is son_task of step 1 * Get step 1 send value : first send * This is segment 2 * This is son_task of step 2 * The first send result : step 2 */
根據上面例子可以得知:第一個send方法,執行了第一個 yield 運算式和第二個 yield 運算式; EXP5
function task(){ echo "This is segment 1\n"; // 這裡是第一程式段,在第一個yield之前; $a = yield son_task(1); echo "Get step 1 send value : $a\n"; echo "This is segment 2\n"; // 這裡是第二程式段,在第一個yield之後,在第二個yield之前; $b = yield son_task(2); echo "Get step 2 send value : $b\n"; echo "This is segment 3\n"; // 這裡是第三程式段,在第二個yield之後,在第三個yield之前; $c = yield son_task(3); echo "Get step 3 send value : $c\n"; echo "This is segment 4\n"; // 這裡是第四程式段,在第三個yield之後;}function son_task($step){ echo "This is son_task of step $step\n"; return "step $step";}$task = task();$sendResult = $task->send('first send');echo "The first send result : $sendResult\n";$sendResult = $task->send('second send');echo "The second send result : $sendResult\n";/** * 輸出: * This is segment 1 * This is son_task of step 1 * Get step 1 send value : first send * This is segment 2 * This is son_task of step 2 * The first send result : step 2 * Get step 2 send value : second send * This is segment 3 * This is son_task of step 3 * The second send result : step 3 */
通過對比EXP4和EXP5得知,第二個send方法僅僅是運行了第三個 yield 運算式,並沒有運行第二個 yield 運算式;
這裡也可以得知,當第一次運營send方法的時候,官網裡面的特殊說明:
如果當前產生器不在yield運算式,它會先自動運行到第一個yield運算式,然後再執行發送$value的操作;因此,沒有必要使用 Generator::next() 來調用PHP產生器(就像在Python中完成的那樣)。
其更準確的含義應該是:如果當前產生器的位置不處於 yield 運算式的話,那麼調用產生器的send方法將會讓產生器自動運行代碼到第一個 yield 運算式,並且執行第一個 yield 運算式;而本次send方法發送的值會作為當前所處的第一個yield運算式的結果值,send方法會讓產生器繼續執行下一個 yield 運算式,並且將下一個 yield 運算式的結果值作為send方法的傳回值;
也就是說,官網裡面的當前所處於 yield 運算式位置的說法,是當前已經執行過得yield運算式;
如果當前處於一個 yield 運算式位置,那麼使用send方法就僅僅替換下當前位置的yield運算式的結果值,執行下一個 yield 運算式並且將結果值作為send方法的傳回值; EXP6
我們現在回頭看看上面所有的例子裡面,第一個運算式裡面的結果並沒有返回;也就是說我們通過send方法是無法獲得產生器裡面第一個 yield 運算式的值的;因為send方法僅僅替換了產生器裡面第一個 yield 運算式的結果值,但是並沒有擷取這個值;所以我們在產生器第一次執行的時候,需要使用current方法來擷取第一個 yield 運算式的值;
function task(){ echo "This is segment 1\n"; // 這裡是第一程式段,在第一個yield之前; $a = yield son_task(1); echo "Get step 1 send value : $a\n"; echo "This is segment 2\n"; // 這裡是第二程式段,在第一個yield之後,在第二個yield之前; $b = yield son_task(2); echo "Get step 2 send value : $b\n"; echo "This is segment 3\n"; // 這裡是第三程式段,在第二個yield之後,在第三個yield之前; $c = yield son_task(3); echo "Get step 3 send value : $c\n"; echo "This is segment 4\n"; // 這裡是第四程式段,在第三個yield之後;}function son_task($step){ echo "This is son_task of step $step\n"; return "step $step";}$task = task();$current = $task->current();echo "This first yield value : $current\n";/** * 輸出: * This is segment 1 * This is son_task of step 1 * This first yield value : step 1 */
同樣對於current方法是不是執行了當前的yield,我們也有疑問,於是: EXP7
function task(){ echo "This is segment 1\n"; // 這裡是第一程式段,在第一個yield之前; $a = yield son_task(1); echo "Get step 1 send value : $a\n"; echo "This is segment 2\n"; // 這裡是第二程式段,在第一個yield之後,在第二個yield之前; $b = yield son_task(2); echo "Get step 2 send value : $b\n"; echo "This is segment 3\n"; // 這裡是第三程式段,在第二個yield之後,在第三個yield之前; $c = yield son_task(3); echo "Get step 3 send value : $c\n"; echo "This is segment 4\n"; // 這裡是第四程式段,在第三個yield之後;}function son_task($step){ echo "This is son_task of step $step\n"; return "step $step";}$task = task();$sendResult = $task->send('first send');echo "The first send result : $sendResult\n";$current = $task->current();echo "This current value : $current\n";/** * 輸出: * This is segment 1 * This is son_task of step 1 * Get step 1 send value : first send * This is segment 2 * This is son_task of step 2 * The first send result : step 2 * This current value : step 2 */
對比EXP6和EXP7,發現current在當產生器當前位置不在yield的時候,也會自動執行到第一個yield位置,並且執行它,這個時候想到了鳥哥說的在產生迭代對象的時候已經隱含地執行了rewind操作;但是經過實驗發現,在產生產生器的時候並沒有執行產生器裡面操作,而是在使用了current,send方法的時候(推測next等方法也可以)自動執行產生器裡面的代碼到第一個yield位置;
那麼既然都有這麼多EXP了,也不差這一個了; EXP8
function task(){ echo "This is segment 1\n"; // 這裡是第一程式段,在第一個yield之前; $a = yield son_task(1); echo "Get step 1 send value : $a\n"; echo "This is segment 2\n"; // 這裡是第二程式段,在第一個yield之後,在第二個yield之前; $b = yield son_task(2); echo "Get step 2 send value : $b\n"; echo "This is segment 3\n"; // 這裡是第三程式段,在第二個yield之後,在第三個yield之前; $c = yield son_task(3); echo "Get step 3 send value : $c\n"; echo "This is segment 4\n"; // 這裡是第四程式段,在第三個yield之後;}function son_task($step){ echo "This is son_task of step $step\n"; return "step $step";}$task = task();$task->rewind();/** * 輸出: * This is segment 1 * This is son_task of step 1 */
現在可以得知,rewind方法就是將產生器執行到第一個yield運算式位置,並且執行第一個yield運算式的操作;
將EXP8和EXP2對比得知,在產生產生器的時候,並沒有執行rewind操作;而是在調用send等方法的時候,才會自動的執行一次rewind方法;