IOLoop.instance().add_callback(self._on_result, result, future)的作用和目的
def async_fetch(self, *args, **kwargs): # Run celery task asynchronous, and get result. future = CeleryFuture() callback = kwargs.pop("callback", None) if callback: IOLoop.instance().add_future( future, lambda future: callback(future.result()) ) result = taskrunner.apply_async(args, **kwargs) self.log.debug(f"Fetch result with status: {result.status}") self.log.debug(f"Fetch result with task_id: {result.task_id}") self.log.debug(f"Fetch result type: {type(result)}") future.set_task_id(result.task_id) self.start = time.time() IOLoop.instance().add_callback(self._on_result, result, future) return future def _on_result(self, result, future): # if result is not ready, add callback function into next loop. async_timeout = self.settings.get("async_timeout", 2) now = time.time() if (now - self.start) >= async_timeout and async_timeout > 0: future.set_exception(Exception("Async task timeout.")) if result.ready(): future.set_result(result.get()) else: IOLoop.instance().call_later(0.01, self._on_result, result, future)
结合上下文代码分析,详解一下 IOLoop.instance().add_callback(self._on_result, result, future)的作用和目的
1. 核心作用
这行代码的作用是:将 _on_result 方法异步添加到 Tornado 的事件循环(IOLoop)中执行,确保 Celery 任务结果的检查不会阻塞当前请求的处理线程。
这是 Tornado 实现非阻塞异步编程的关键机制。
2. 逐层拆解
(1) IOLoop.instance()
-
含义:获取当前 Tornado 应用的全局事件循环实例(单例模式)。
-
作用:Tornado 的 IOLoop 是事件驱动的核心,负责调度所有异步任务(如网络 I/O、回调等)。
(2) add_callback(callback, *args, **kwargs)
-
含义:将一个回调函数
callback添加到事件循环的下一个迭代中执行。 -
关键特性:
-
非阻塞:不会立即执行,而是由 IOLoop 在合适的时机调用。
-
线程安全:即使从其他线程调用,也能安全地将任务转移到主线程执行。
-
异步执行:适合处理耗时操作(如轮询 Celery 任务状态)。
-
(3) self._on_result(result, future)
-
含义:检查 Celery 任务结果的回调函数。
-
逻辑:
-
如果任务完成(
result.ready()),通过future.set_result()返回结果。 -
如果未完成,通过
call_later延迟 0.01 秒后再次检查(递归调用)。
-
3. 代码执行流程
def async_fetch(self, *args, **kwargs):
future = CeleryFuture()
result = taskrunner.apply_async(args, **kwargs) # 启动 Celery 任务
future.set_task_id(result.task_id)
IOLoop.instance().add_callback(self._on_result, result, future) # 关键行
return future
-
任务提交:调用
apply_async提交 Celery 任务,返回AsyncResult对象。 -
异步轮询:通过
add_callback将_on_result加入事件循环,不阻塞当前请求。 -
结果返回:当任务完成时,
_on_result通过future.set_result()通知调用方。
4. 为什么需要 add_callback?
-
避免阻塞:直接同步等待
result.get()会阻塞 Tornado 的 IOLoop,导致其他请求无法处理。 -
事件驱动:Tornado 是单线程异步框架,所有耗时操作必须通过回调或协程非阻塞处理。
-
资源高效:相比多线程/多进程,用事件循环调度更轻量。
5. 对比同步阻塞写法
错误写法(阻塞):
def async_fetch_blocking(self):
result = taskrunner.apply_async().get() # 同步阻塞等待
return result
-
问题:
get()会阻塞整个 Tornado 进程,其他请求无法响应。
正确写法(非阻塞):
def async_fetch(self):
future = CeleryFuture()
IOLoop.instance().add_callback(self._on_result, result, future) # 异步轮询
return future
-
优势:立即返回
future,由事件循环在后台检查任务状态。
6. 关联技术点
-
CeleryFuture的作用:
继承自tornado.concurrent.Future,用于桥接 Tornado 的异步模型和 Celery 的任务结果。 -
_on_result的递归调用:
通过call_later实现轮询,直到任务完成或超时。
7. 总结
| 组件 | 作用 |
|---|---|
IOLoop.instance() |
获取全局事件循环实例 |
add_callback() |
将函数异步调度到事件循环,确保非阻塞 |
_on_result() |
轮询 Celery 任务状态,并通过 Future 返回结果 |
这行代码是 Tornado + Celery 异步协作的核心,保证了高并发下服务不会被阻塞。
二:问题
IOLoop.instance().add_callback(self._on_result, result, future) return future
如果没有结果返回 会在这里阻塞吗?
结合上下文代码和 Tornado 的异步机制,IOLoop.instance().add_callback(self._on_result, result, future) 不会阻塞当前请求,而 return future 也不会阻塞。以下是详细分析:
1. 关键结论
-
add_callback不会阻塞:它是非阻塞的,仅将回调函数_on_result加入事件循环队列,立即返回。 -
return future不会阻塞:返回的是CeleryFuture对象(异步占位符),实际结果由事件循环后续填充。 -
阻塞风险仅发生在
await future时:如果外部调用者使用await或yield等待future,才会挂起当前协程(但这是异步等待,而非阻塞线程)。
2. 代码执行流程
(1) 非阻塞的 add_callback
IOLoop.instance().add_callback(self._on_result, result, future)
-
行为:
-
将
_on_result(result, future)添加到 Tornado 事件循环的任务队列。 -
立即返回,不会等待
_on_result执行。
-
-
类比:
类似于 JavaScript 的setTimeout(fn, 0),只是计划任务,不阻塞当前代码。
(2) 立即返回 future
return future
-
future的状态:-
此时
future尚未完成(not future.done())。 -
它是一个“承诺”,表示结果将在未来通过
future.set_result()填充。
-
-
调用方的选择:
-
如果调用方直接
return future(如 Tornado Handler 中),请求会立即结束,但响应未完成(长轮询)。 -
如果调用方使用
await future,会挂起当前协程,但释放线程去处理其他任务(异步等待)。
-
3. 阻塞场景对比
阻塞写法(错误示例)
def blocking_fetch(self):
result = taskrunner.apply_async().get() # 同步阻塞调用
return result
-
问题:
get()会同步等待 Celery 任务完成,阻塞整个 Tornado IOLoop 线程。
非阻塞写法(当前代码)
def async_fetch(self):
future = CeleryFuture()
IOLoop.instance().add_callback(self._on_result, result, future) # 非阻塞
return future # 立即返回未完成的 future
-
优势:
-
即使 Celery 任务未完成,
async_fetch()也会立即返回。 -
Tornado 可以继续处理其他请求,直到
_on_result通过事件循环设置结果。
-
4. 异步结果传递机制
(1) _on_result 的工作流程
def _on_result(self, result, future):
if result.ready():
future.set_result(result.get()) # 填充结果
else:
IOLoop.instance().call_later(0.01, self._on_result, result, future) # 延迟重试
-
递归延迟检查:
-
若任务未完成,通过
call_later再次调度_on_result(非阻塞)。 -
直到任务完成或超时。
-
(2) 调用方的异步等待
# 在 Tornado Handler 中:
async def post(self):
future = self.async_fetch() # 返回未完成的 future
script_rs = await future # 异步等待结果(非阻塞)
-
await future的行为:-
若
future未完成,挂起当前协程,但释放 IOLoop 线程去处理其他任务。 -
当
future.set_result()被调用时,恢复协程并继续执行。
-
5. 总结
| 操作 | 是否阻塞 | 说明 |
|---|---|---|
add_callback(_on_result) |
❌ 不阻塞 | 仅将回调加入事件循环队列,立即返回。 |
return future |
❌ 不阻塞 | 返回未完成的 Future 对象,实际结果由事件循环后续填充。 |
await future(调用方) |
⚠️ 异步挂起 | 挂起当前协程但不阻塞线程,属于异步等待。 |
同步调用 future.result() |
✅ 阻塞 | 强制同步等待结果(错误用法,会破坏异步模型)。 |
6. 为什么不会阻塞?
-
Tornado 的事件循环模型:
IOLoop 是单线程的,所有耗时操作必须通过回调或协程异步处理。add_callback和Future的设计正是为了避免阻塞。 -
Celery 的异步协作:
通过轮询result.ready()而非同步等待get(),保证 IOLoop 不被卡住。
7. 性能影响
-
延迟检查间隔(
call_later(0.01)):
每隔 10 毫秒检查一次任务状态,对 CPU 的开销极小。 -
超时控制(
async_timeout):
默认 2 秒超时防止任务长时间挂起。
最终答案
add_callback + return future 的组合不会阻塞当前请求,它们是 Tornado 异步编程的核心模式。阻塞仅发生在显式同步等待结果时(如 future.result()),而正确的 await future 是异步挂起,不会阻塞线程。

浙公网安备 33010602011771号