КомпонентыChannel::recv

Channel::recv

(PHP 8.6+, True Async 1.0)

php
public Channel::recv(?Completable $cancellationToken = null): mixed

Получает следующее значение из канала. Операция является блокирующей — текущая корутина приостанавливается, если в канале нет доступных значений.

Если канал закрыт и буфер пуст, выбрасывается ChannelException. Если канал закрыт, но в буфере остались значения, они будут возвращены.

ChannelException означает «канал закрыт» — но не только штатным close(). Тем же исключением сообщается о дедлоке (NO_PRODUCERS, NO_CONSUMERS, DEADLOCK) и об исчезновении канала (DISPOSED, SCOPE_DISPOSED). Отличать их нужно по свойству $e->reason (перечисление ChannelCloseReason).

Поэтому catch (ChannelException) { break; } опасен: он превращает дедлок в «поток штатно кончился», и корутина молча завершается с нулём обработанных значений. Проверяйте reason — либо используйте foreach, который сам завершается при штатном закрытии и пробрасывает всё остальное.

Параметры

cancellationToken : Токен отмены (Completable), позволяющий прервать ожидание по произвольному условию. null — ожидание без ограничений (по умолчанию). Когда токен завершается, операция прерывается и выбрасывается AsyncCancellation. Для ограничения по времени можно использовать Async\timeout().

Возвращаемые значения

Следующее значение из канала (mixed).

Ошибки

  • Выбрасывает Async\ChannelException, если канал закрыт и буфер пуст.
  • Выбрасывает Async\AsyncCancellation, если токен отмены был завершён.

Примеры

Пример #1 Получение значений из канала

php
<?php

use Async\Channel;

$channel = new Channel(5);

spawn(function() use ($channel) {
    for ($i = 1; $i <= 5; $i++) {
        $channel->send($i);
    }
    $channel->close();
});

spawn(function() use ($channel) {
    try {
        while (true) {
            $value = $channel->recv();
            echo "Получено: $value\n";
        }
    } catch (\Async\ChannelException $e) {
        // Дедлок и потеря канала приходят тем же исключением — их нельзя принимать
        // за штатный конец потока, иначе ошибка исчезнет без следа.
        if ($e->reason !== \Async\ChannelCloseReason::EXPLICIT) {
            throw $e;
        }

        echo "Канал закрыт и пуст\n";
    }
});

То же самое, но без ручной проверки — foreach завершается сам при штатном закрытии, а дедлок и потерю канала пробрасывает наружу:

php
spawn(function() use ($channel) {
    foreach ($channel as $value) {
        echo "Получено: $value\n";
    }

    echo "Канал закрыт и пуст\n";
});

Пример #2 Получение с таймаутом

php
<?php

use Async\Channel;

$channel = new Channel();

spawn(function() use ($channel) {
    try {
        $value = $channel->recv(Async\timeout(2000));
        echo "Получено: $value\n";
    } catch (\Async\AsyncCancellation) {
        echo "Данные не поступили за 2 секунды\n";
    }
});

Пример #3 Получение с произвольным токеном отмены

php
<?php

use Async\Channel;
use Async\Future;

$channel = new Channel();
$cancel = new Future();

spawn(function() use ($channel, $cancel) {
    try {
        $value = $channel->recv($cancel);
        echo "Получено: $value\n";
    } catch (\Async\AsyncCancellation) {
        echo "Получение отменено\n";
    }
});

// Отменяем из другой корутины
spawn(function() use ($cancel) {
    Async\delay(500);
    $cancel->complete(null);
});

См. также