Workerman中禁用sleep(),改用Timer::add()实现异步延时;需检查连接状态、绑定上下文、避免内存泄漏,并合理管理定时器生命周期。

Workerman里用sleep()会导致进程阻塞,必须换掉
Workerman是常驻内存的异步事件驱动框架,所有逻辑跑在单线程(或多个worker进程)的事件循环里。sleep()这种同步阻塞调用会卡住整个进程,后续所有连接、定时器、IO都停摆——不是“延时”,是“瘫痪”。你看到的连接超时、心跳断开、定时器失灵,八成就是偷偷用了sleep()。
Timer::add()是最直接的替代方案,但要注意回调执行时机
它本质是注册一个异步回调,在指定毫秒后由事件循环触发。和sleep()的“暂停当前流程”完全不同,它是“预定一个将来要做的事”。
- 回调函数里不能写阻塞操作(比如
file_get_contents()、mysqli_query()),否则照样卡死 - 如果需要链式延时(比如延时1秒→再延时2秒→再执行),得在第一次回调里再调一次
Timer::add(),不能靠嵌套sleep()模拟 - 定时器默认只执行一次;需要重复执行,得传
true作为第四个参数:Timer::add(1000, [$obj, 'method'], [], true) - 记得在不需要时手动清除:
Timer::del($timerId),否则内存泄漏+定时器堆积
延时发消息/重试场景下,别把Timer::add()写在onMessage里就完事
常见错误:客户端发来指令,你想“5秒后推送结果”,于是在onMessage里直接Timer::add(5000, function() { ... })——问题在于:这个回调执行时,原始的$connection可能已经断开、被回收,甚至$this指向已失效。
- 务必在回调里先检查连接状态:
if ($connection->isConnected()) { $connection->send(...); } - 如果要绑定上下文(比如用户ID、任务ID),用闭包use传值,别依赖外部变量引用:
Timer::add(5000, function() use ($uid, $connection) { ... }) - 高频触发场景(如每秒上百次延时任务),避免无节制创建定时器,考虑用队列+单个长周期定时器轮询
复杂延时逻辑建议封装成独立类,别堆在Worker里
比如“下单后30分钟未支付自动关单”,涉及时间计算、状态检查、DB更新、通知推送——全塞进Timer::add()回调里,可读性差、难测试、没法复用。
- 抽成
OrderTimeoutHandler类,构造时传入订单ID和Worker实例 - 在
__invoke()或handle()里做完整业务逻辑,包括异常捕获和重试策略 - 延时触发时,new一个实例并调用方法,比裸写匿名函数更可控
- 注意:Workerman不支持跨进程共享定时器,多worker时需配合Redis锁或延迟队列防重复执行
真正容易被忽略的是定时器的生命周期管理——没人清理的Timer::add()就像没关的数据库连接,跑几天后worker内存暴涨、响应变慢,错误日志里却找不到明显报错。










