跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

如何处理Hyperf协程中抛出的异常无法被全局捕获_在子协程内手动尝试catch

Hyperf 中子协程(co/go)抛出的未捕获异常默认不被全局 ExceptionHandler 捕获,会直接终止协程且不冒泡;必须在子协程内手动 try/catch 并记录日志,不可依赖外部处理器。 Hyperf 中子协程(如
co
、
go
、
Coroutine::create
)抛出的未捕获异常,**默认不会被全局
ExceptionHandler
捕获**——它会直接终止该协程,且不冒泡到父协程或 HTTP 请求生命周期中。这是协程调度模型决定的,不是配置遗漏。 为什么子协程异常不走全局 ExceptionHandler Hyperf 的全局异常处理器(如
App\Exception\Handler\AppExceptionHandler
)只接管「HTTP 请求主协程」中未被捕获的异常。而
co
或
go
启动的是独立协程,其异常由 Swoole 协程调度器直接处理,不经过 Hyperf 的请求上下文和异常分发链。 错误现象:子协程内
throw new RuntimeException('xxx')
,日志里只有
PHP Fatal error: Uncaught RuntimeException
,但
AppExceptionHandler::handle
完全没执行 根本原因:子协程与 HTTP 请求协程是平行关系,不是父子继承关系;异常无法跨协程边界自动传播 影响:这类异常不会记录到
exceptions.php
配置的日志处理器中,也不会返回标准错误响应,容易漏监控 必须在子协程内手动 try/catch 这是最直接、最可控的解法。不能依赖外部捕获,必须在协程启动的闭包内部兜底。 正确写法:每个
co
/
go
闭包第一层就加
try/catch
,并显式记录或上报 别偷懒写成
co(function () { /* 大段逻辑 */ });
—— 里面任何位置抛异常都会静默失败 建议统一用
Hyperf\Logger\LoggerFactory
记录,带上
coroutine_id()
方便追踪 示例:
use Hyperf\Logger\LoggerFactory; use Hyperf\Coroutine\Coroutine;

$logger = $this->container->get(LoggerFactory::class)->get('job');

Hyperf 3.1.66

本页面提供企业级 PHP 协程框架 Hyperf 3.1.66 版本的官方源码下载与完整更新日志。重点解析 v3.1.66 版本中新增的 gRPC 多客户端负载均衡支持、Pool 连接池全量刷新、Guzzle 持久化 Cookie 以及数据库 JSON 包含键查询等核心优化特性。

下载

co(function () use ($logger) { try { // 业务逻辑,可能 throw $result = $this->heavyTask(); $logger->info('sub-coroutine success', ['cid' => coroutine_id()]); } catch (\Throwable $e) { // 必须在这里处理,否则异常丢失 $logger->error('sub-coroutine failed', [ 'cid' => coroutine_id(), 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(), ]); // 可选:上报 Sentry / Prometheus 等 } });

避免用 set_exception_handler 替代 有人试图在子协程里调用
set_exception_handler
,这是无效的:
set_exception_handler
是 PHP 进程级的,对协程无感知,Swoole 协程中触发时不会进入该回调 Hyperf 的
ExceptionHandler
机制本身也不作用于子协程,它是基于
Hyperf\HttpServer\Middleware\ErrorMiddleware
绑定在请求生命周期上的 强行注册反而可能污染 Worker 进程的全局状态,导致后续请求异常处理错乱 进阶:封装安全协程启动工具 如果项目中大量使用子协程,重复写
try/catch
易出错。可封装一个安全版启动函数:
function safe_co(callable $callable, string $context = 'unknown'): void { co(function () use ($callable, $context) { try { $callable(); } catch (\Throwable $e) { $logger = \Hyperf\Utils\ApplicationContext::getContainer() ->get(\Hyperf\Logger\LoggerFactory::class) ->get('coroutine'); $logger->error("[$context] unhandled exception", [ 'cid' => \Hyperf\Coroutine\Coroutine::id(), 'msg' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); } }); }

// 使用 safe_co(fn() => $this->sendNotification(), 'notify_user');

注意:这个封装不能替代业务层的精准错误处理,仅作为兜底。真正需要重试、降级或用户提示的逻辑,仍得在
try
块内明确实现。 子协程异常不冒泡是协程模型的固有特性,不是 bug。指望全局处理器一劳永逸地覆盖所有协程,只会让问题更隐蔽——日志缺失、监控断点、线上故障难复现。最稳妥的做法,就是把每个
co
当作一个微型“请求”来对待:自己负责初始化、自己兜底异常、自己打标日志。

相关文章