yield和send的執行循序徹底搞清


yield:

對於yield方法和Generator的send同時使用時的執行順序一直搞不清,今天看到這篇

理解PHP中的Generator

加上測試,終於搞清了。

總結一下上文中的結論:

  • Generator提供了一種方便的實現簡單的Iterator(迭代器)的方式,使用Generator實現Iterator不需要創建一個類來繼承Iterator接口。
  • Generator實現了Iterator中的5個方法,還提供了三個新方法,其中__wakeup是一個魔術方法,用於序列化,Generator實現這個方法是為了防止序列化。
  • Generator 對象不能通過 new 實例化。
  • yield關鍵字只能在函數中使用(你可以嘗試下在函數外使用,看看會發生什么),而且使用了yield關鍵字的函數都會返回一個Generator對象。
  • yield語句有點像return語句,代碼執行到yield語句,generator函數的執行就會終止,並且會返回yield語句中的表達式的值給 Generator對象,這跟return語句一樣,不同的是,這返回值只是作為遍歷Generator對象的當前元素,而不能賦值給其他變量。
  • 當對Generator對象繼續迭代,generator函數中的yield后面的代碼會繼續執行,直到generator函數中的yield語句全部執 行完畢,或者是碰到generator函數中的空return語句(返回null的return語句),在generator函數中使用帶有非null返 回值的return語句會報編譯錯誤。
  • 如果yield后面沒有任何表達式(變量、常量都是表達式),那么它會返回NULL,這一點跟return語句一致。
  • yield也可以返回鍵值對的形式。
  • 在send之前, 當$gen迭代器被創建的時候一個rewind()方法已經被隱式調用,這樣rewind的執行將會導致第一個yield被執行, 並且忽略了他的返回值.真正當我們調用yield的時候, 我們得到的是第二個yield的值! 導致第一個yield的值被忽略。(這條來自鳥哥博客的總結,可以看“多任務協作”部分的例子理解)

 

幾個經典的例子幫助理解!

1.經典的例子熱身

function xrange($start, $end, $step = 1) { for ($i = $start; $i <= $end; $i += $step) { yield $i; } } foreach (xrange(1, 1000000) as$num) { echo$num, "\n"; }
$range = xrange(1, 1000000); var_dump($range); // object(Generator)#1
var_dump($range instanceof Iterator); // bool(true)

2.鳥哥博客中的例子

在PHP中使用協程實現多任務調度,(確定要搞清楚為什按照這種方式輸出. 以便后續繼續閱讀.的例子)在上邊連接文章里有詳細的解釋了。

3.一個讀取文件的例子,同時使用了send函數

/* a.log的內容 */
aaaaa
bbbbb
ccccc
ddddd
function lineGenerator($file)
{
    $fp = fopen($file, 'rb');
    while ($line = fgets($fp)) {
        var_dump(yield $line);
    }
}

$lines = lineGenerator("a.log");
foreach ($lines as $line) {
    $lines->send('test');
    echo $line;
}
/* 輸出結果 */

string(4) "test" aaaaa NULLstring(4) "test" ccccc NULL

之前對於這個輸出結果一直理解不了,現在總結一下怎么分析:

1.外層循環的每一次都會調用一次內層函數中yield的一行,執行完一次yield便停止執行。

2.對於var_dump(yield $line) 這樣的寫法,在分析時可以拆分為$var = (yield $line);var_dump($var);便於理解。

3.send()函數在調用時如果yield函數一次也沒被執行過,則會先執行一次yield(其實是在創建迭代器時已經隱式的執行了rewind方法),再進行賦值,再執行next(send有一個next的功能)。

根據上邊的方法分析一下上邊的例子:

1.首先,根據總結的方法2先對lineGenerator函數進行修改,因為a.log就4行,索性可以不用循環了。修改后如下:

function lineGenerator($file)
{
    $fp = fopen($file, 'rb');
    $var = (yield fgets($fp));
    var_dump($var);
    $var = (yield fgets($fp));
    var_dump($var);
    $var = (yield fgets($fp));
    var_dump($var);
    $var = (yield fgets($fp));
    var_dump($var);
}

2.foreach開始時,($lines as $line)肯定是調用了current()方法賦值$line,就是內存循環要執行一次yield,此時內層代碼執行到第二行,$line被賦值aaaaa。

3.接着,外層循環調用了$lines->send('test'),這時的test值會賦值給內層函數當前的yield(就是第一個yield),然后執行第一個var_dump(),打印出了test,然后執行第二個yield,並把yield的值賦給send函數的返回值(就是bbbbb),內層代碼停止。

4.接着,外層循環執行了echo $line;所以打印出aaaaa

5.foreach 進入下次循環,就是需要從函數上次停下的位置執行到下一個yield執行完,函數先執行第二個var_dump(),此時當前的yield是NULL,所 以打印出NULL,接着執行第三個yield,獲取到a.log的第三行賦值給$line,即ccccc,函數執行停止。

6.然后循環執行$lines->send('test'),函數的第三個var_dump()就打印出test,執行第4個yield,把a.log的ddddd賦給send的返回值。函數執行停止。

7.外層循環執行echo $line;打印出ccccc

8.foreach進入下一次循環,函數又要從上次停止的位置執行到下一個yield結束,就是函數中最后一個var_dump()執行,打印出NULL,因為后邊沒有yield了,代碼執行結束。

4.一個日志寫入的例子,可以說明調用send時沒有調用過yield的情況,用總結的第三個方法解決。

function logger($fileName)
{
    $fileHandle = fopen($fileName, 'a');
    while (true) {
        echo "aaa\n";
        fwrite($fileHandle, yield."\n");
        echo "bbb\n";
    }
}

$logger = logger('a.log');
var_dump($logger->send('Foo'));
var_dump($logger->send('Bar'));
/* 輸出結果*/
aaa
bbb
aaa
NULL
bbb
aaa
NULL

分析:

1.外部第一行$logger = logger('a.log'),此時只是生成了一個Generator對象,yield並沒有執行。

2.外部第二行$logger->send('Foo'),由於函數內部沒有yield,所以會先執行一次yield后再執行賦值,next()操作。執行一次yield,會打印出aaa,注意yield執行完但是fwrite並沒有執行,(這個可以算是send函數的初始化操作,哈哈)

接着才是像正常情況下的send執行一樣,進行next()操作,先把Foo賦值給當前的yield,執行fwrite寫入,然后打印bbb,再打印aaa,執行

fwrite($fileHandle,yield . "\n");注意fwrite不執行,由於yield后邊是空的,所以send的返回值賦值為NULL,內部函數停止,外部的第一個var_dump()會打印一個NULL

3.外部第三行執行$logger->send('Foo'),此時內部函數整執行到fwrite,send把Foo賦值給當前的yield,然后fwrite寫入,然后打印bbb,再打印aaa,執行到fwrite($fileHandle,yield . "\n")停止執行。fwrite不執行,yield的返回值NULL,所以外部第二個var_dump()打印NULL
 
附:yield輸出key,value:
$lineParts = explode(' ', $line, 2);
yield $lineParts[0] => $lineParts[1];

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM