Swoole 4 中 Channel 关闭后读写会抛出必须在当前协程内捕获的 ChannelException;关闭后 pop/push 均触发“Channel is closed”异常,空通道未关闭时 pop 仅挂起;跨协程传递通道需统一关闭管理,推荐用 isClosed() 预检;ChannelException 与 ExitException 均不可跨协程捕获。

在 Swoole 4 中,协程间通过 Swoole\Coroutine\Channel 通信时,通道关闭后继续读写会抛出 Swoole\Coroutine\Channel\Exception(继承自 RuntimeException),该异常必须在**当前协程内捕获**,无法跨协程传递或由父协程统一处理。
通道关闭后的典型异常场景
当调用 $channel->close() 后:
-
读操作(
pop()):立即抛出Channel is closed异常; -
写操作(
push()):同样抛出Channel is closed异常; - 即使通道已空但未关闭,
pop()会挂起协程(非异常),直到有数据或被关闭。
必须在协程内 try-catch 捕获
协程异常不跨边界传播,以下写法是安全且推荐的:
go(function () {
$chan = new Swoole\Coroutine\Channel(1);
$chan->close();
try {
$chan->pop(); // 触发 Channel is closed
} catch (Swoole\Coroutine\Channel\Exception $e) {
echo "捕获到通道异常: " . $e->getMessage() . "\n";
}
try {
$chan->push('data');
} catch (Swoole\Coroutine\Channel\Exception $e) {
echo "写入已关闭通道失败: " . $e->getMessage() . "\n";
}
});
嵌套协程中通道的生命周期管理
若通道在父协程创建、传入子协程使用,需注意:
- 通道对象本身可跨协程传递(是引用类型),但关闭动作影响所有持有者;
- 避免多个协程竞态调用
close(),建议由单一协程负责关闭(如生产者结束时); - 消费者协程应主动检查
$chan->isClosed()(Swoole ≥ 4.5.0 支持),而非仅依赖异常; - 不建议在
__destruct或finally块中无条件 close,可能引发重复关闭异常。
与 exit() 和 ExitException 的区别
通道异常(Channel\Exception)和 exit() 触发的 Swoole\ExitException 无关,二者捕获方式不同:
-
Channel\Exception是运行时逻辑异常,属于Throwable,用常规catch (\Throwable)即可捕获; -
ExitException专用于exit()中断协程流程,需显式catch (\Swoole\ExitException); - 两者均不可被外部协程捕获,必须在触发它们的同一协程作用域内处理。


















