Symfony 5 的异步任务必须依赖 Messenger+Doctrine transport 等消息队列方案,Process::start() 和 sleep() 无法实现真正异步;需配置 messenger.yaml、创建表、使用 $messenger->dispatch() 发送可序列化消息,并常驻运行 consumer 命令。

Symfony 5 的异步任务不能靠 Process::start() 或控制器里加个 sleep() 实现——它本身不提供运行时级异步,必须借力消息队列或系统级进程管理。
用 Messenger + Doctrine transport 最快落地
这是 Symfony 5 官方推荐、无需额外服务(如 Redis/RabbitMQ)就能跑起来的方案,适合中小项目。
- 确保已安装
symfony/messenger和doctrine/doctrine-bundle - 在
config/packages/messenger.yaml中启用 Doctrine transport:transports: { async: 'doctrine://default' } - 添加路由规则:
framework.messenger.routing.Symfony\Component\Messenger\MessageBusInterface: 'async'(注意:Symfony 5.4+ 推荐直接路由具体消息类) - 运行
php bin/console doctrine:migrations:diff && php bin/console doctrine:migrations:migrate创建messenger_messages表 - 发送任务时别调
$bus->dispatch($msg),而是用$messenger->dispatch(new YourCustomMessage(...));YourCustomMessage必须是可序列化的普通 PHP 类
启动消费者:php bin/console messenger:consume async --limit=10 --time-limit=3600。这个命令得常驻运行(建议用 systemd 或 supervisord 管理)。
Process::start() 在 HTTP 请求里根本不算“异步”
很多人误以为调 $process->start() 就能甩开脚本去后台跑,结果备份/导出中途静默失败——因为 PHP-FPM 进程一结束,子进程就被 kill 或孤儿化。
-
start()后不wait()就返回响应,子进程大概率被 Web 服务器回收(尤其 Apache 或超时配置严的 Nginx+PHP-FPM) -
disableOutput()->setTimeout(null)->start()看似脱离输出绑定,但依然依赖父 PHP 进程存活,在 FPM 下极不可靠 - 真要本地调试 shell 脚本,可用
exec('nohup /path/to/script.sh > /dev/null 2>&1 & echo $!'),但需自行管 PID、日志、错误码,Messenger 才是正路
发 HTTP 请求异步?Guzzle Promise 是主力
Symfony 自身不处理 HTTP 异步,得靠 Guzzle(推荐 7.x)配合 Promise 实现并发请求,比如批量调第三方 API。
- 装
guzzlehttp/guzzle和guzzlehttp/promises - 构造多个
$client->getAsync()或$client->postAsync(),得到 Promise 数组 - 用
Promise\all()或Promise\settle()统一 await,不要一个个->wait() - 别混用
curl_multi—— 缺少异常上下文、日志埋点难、无法接入 Symfony 事件系统
注意:这仍是“单请求内并发”,不是“卸载到后台”,控制器执行完才会返回响应。
Mailer 异步必须走 Messenger,绕不开
哪怕只是发一封欢迎邮件,只要想异步,就必须把 Email 包进 SendEmailMessage 并 dispatch,否则就是同步阻塞。
- 确认
config/packages/mailer.yaml里transport配的是实际可用的(如smtp://...),否则队列消费时会报 transport not found - 别手写
new SendEmailMessage($email)后直接->send(),那等于没走队列 - 若用
TemplatedEmail,一样要包进去;Messenger 内部会自动 resolve Twig 模板 - Doctrine transport 在 PHP 8.5.7 下默认用
PhpSerializer,如果项目全局配了 JSON 序列化器,反序列化Email时可能丢类型信息,报错类似Class "Symfony\Component\Mime\Email" not found
真正麻烦的从来不是“怎么写 dispatch”,而是 consumer 进程是否活着、transport 是否写权限正确、序列化器是否兼容——这些地方一漏,任务就卡在数据库里不动。


















