celery任务异常默认不传播到调用方,异步提交时失败无感知;需显式启用on_failure钩子、retry重试或get()同步获取(慎用),且异常序列化后丢失堆栈细节。

任务失败时异常默认不传播到调用方
直接调用 task.apply().get() 或 task.delay().get() 时,如果任务内部抛出异常,.get() 会重新抛出该异常——但这只在你主动阻塞等待结果时才发生。而绝大多数生产场景下,任务是纯异步提交(只调用 .delay() 或 .apply_async(),不跟 .get()),此时异常根本不会到达发起请求的进程,也不会打印到调用方日志里。
常见错误现象:task.delay() 看似成功返回一个 AsyncResult 对象,但任务实际在 worker 进程崩溃了,调用方却毫无感知,重试、告警、补偿逻辑全部失效。
- 必须显式启用异常捕获机制,不能依赖“自动上报”
- worker 进程中的异常默认只记录在 worker 日志中,和 Django/Flask 主进程日志完全隔离
-
task.ignore_result = True(默认)时,连.get()都不可用,异常更无从获取
用 task.on_failure 钩子做失败后处理
on_failure 是 Celery 提供的实例级回调,在 worker 执行失败后、退出前触发,参数包含异常实例、任务 ID、args、kwargs 等,适合做日志增强、告警、状态更新。
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
@app.task(bind=True)
def risky_task(self, x, y):
if x 绑定失败钩子<p>risky_task.on_failure = lambda exc, task_id, args, kwargs, einfo: (
logger.error(f"Task {task_id} failed: {exc!r}, traceback: {einfo.traceback}"),
send_alert(f"CELERY FAIL: {risky_task.name} → {exc}"),
)
</p>
- 必须设为
bind=True,否则self不可用,无法绑定on_failure -
einfo是celery.exceptions.ExceptionInfo对象,einfo.traceback是字符串格式完整堆栈 - 钩子运行在 worker 进程内,不能直接操作主应用的 DB 连接(如 Django ORM),需重建连接或走消息队列
用 Task.retry() 控制重试而非放任崩溃
不是所有失败都要告警;网络抖动、临时锁冲突等可恢复错误应自动重试。retry() 能把任务重新入队,避免进入失败状态,也绕过 on_failure。
@app.task(bind=True, autoretry_for=(ConnectionError, redis.ConnectionError), retry_kwargs={'max_retries': 3})
def fetch_remote_data(self, url):
try:
return requests.get(url).json()
except Exception as exc:
# 显式触发重试(等价于 raise self.retry(...))
raise self.retry(exc=exc, countdown=2 ** self.request.retries)
-
autoretry_for参数只对未捕获异常生效;一旦你在函数内try/except了,就必须手动调用self.retry() -
countdown建议用指数退避(2 ** retries),避免雪崩 - 重试次数超限后,异常才会真正抛出并触发
on_failure
用 result.get(propagate=True) 主动拉取异常(慎用)
仅当业务逻辑必须同步确认任务成败时(例如支付回调后续动作),才用 .get() 拉取结果。注意:它会阻塞当前线程,且默认 propagate=True,即失败时原样抛出 worker 中的异常。
result = risky_task.delay(-1, 2)
try:
data = result.get(timeout=10) # 阻塞最多 10 秒
except ValueError as e:
logger.warning(f"Business rule violation: {e}")
except celery.exceptions.TimeoutError:
logger.error("Task took too long")
except Exception as e:
logger.error(f"Unexpected task failure: {e}")
-
timeout必须设,否则可能永久阻塞(尤其 worker 挂了或网络中断) - 不要在 Web 请求中直接调用
.get(),会拖垮整个 HTTP worker(如 Gunicorn 的 sync worker) - 若任务设置了
ignore_result=True,.get()永远返回None,无法感知成败
最易被忽略的一点:Celery 的异常对象在 worker 和 client 之间序列化传输时,只保留类型名和 message,原始 traceback、局部变量、自定义属性全丢失。真要调试,得去 worker 日志里翻 einfo.traceback,而不是指望客户端拿到的异常能打出来完整上下文。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










