Python中如何在Tornado框架内使用Requests等HTTP SDK实现非阻塞编程?

我知道 tornado 有 AsyncHttpClient,用它 + callback 肯定没错。

但是有的时候我用的可能是 SDK,比如 SDK 访问一个 API,一次 http 请求对面要 2 秒才能响应。

那么如果是 gevent,我理解他有猴子补丁,可以在我网络阻塞的时候,切到别的任务上工作,等到网络连接结束的时候,再切回来。

我的问题是:

  1. tornado 是不是 不能在我使用基于 Http 的 SDK,或者 Requests 库的时候,自动识别我网络请求阻塞了,然后处理别的任务?

  2. 如果在 tornado 下不使用 AsyncHttpClient,达到我的目的呢?

搜了一圈谷歌,全是给我讲 io 多路复用和信号驱动,很少有提到这块相关的


Python中如何在Tornado框架内使用Requests等HTTP SDK实现非阻塞编程?

7 回复

用异步的 HTTPClient 库就可以了,比如 aiohttp。
如果你用的那个 SDK 本身没有用异步的库,那么你要么把他重写成异步的,要么用类似 asyncio.run_in_executor 的方式去运行。


在Tornado里直接用requests会阻塞整个事件循环,因为它不是异步的。你得用AsyncHTTPClient或者aiohttp。不过如果你非要把requests这种同步库塞进去,那就得用线程池,用run_on_executor把它扔到后台去跑,别卡住主循环。

下面是个具体的例子:

import tornado.ioloop
import tornado.web
import tornado.concurrent
from concurrent.futures import ThreadPoolExecutor
import requests

# 搞个线程池,专门处理这些阻塞操作
executor = ThreadPoolExecutor(max_workers=4)

class MainHandler(tornado.web.RequestHandler):
    # 把这个执行器绑到Handler上
    executor = executor

    @tornado.concurrent.run_on_executor
    def blocking_fetch(self, url):
        # 这个会在线程池里执行,不会阻塞IOLoop
        response = requests.get(url)
        return response.text

    async def get(self):
        url = "http://httpbin.org/delay/2"  # 一个会延迟2秒的测试地址
        try:
            # 异步等待线程池的任务完成
            result = await self.blocking_fetch(url)
            self.write(f"Got result: {result[:100]}...")  # 只显示前100字符
        except Exception as e:
            self.write(f"Error: {e}")
        self.finish()

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    print("Server starting on port 8888...")
    tornado.ioloop.IOLoop.current().start()

核心思路

  1. 搞个ThreadPoolExecutor线程池。
  2. @run_on_executor装饰器把你那个会阻塞的requests.get()包装一下,让它跑到线程池里去。
  3. 在异步的get()方法里用await调用这个包装好的函数。

这样,requests在后台线程里跑,Tornado的主事件循环就能继续处理其他请求,不会卡住。当然,这本质上还是线程,不是真正的协程异步。如果项目允许,直接换用aiohttp或Tornado自带的AsyncHTTPClient是更地道、性能也更好的选择。

总结建议:用线程池包装同步请求,或者直接换用异步HTTP客户端。

不能自动把同步变成异步处理请求,除非是 gevent 那种猴子布丁。用 asyncio.run_in_executor 是新起线程去执行

#2 asyncio.run_in_executor 好的好的,我研究一下。

#4 感谢,对我来说明显第二种合适。

> 使用 gen.coroutine 装饰器编写异步函数,如果库本身不支持异步,那么响应任然是阻塞的。

第二种方法,ThreadPoolExecutor,起了一个另外的线程去做事情,能满足我的需求。
之前一直不敢用的原因是,不清楚这种方法,到底是不是在主线程里做的,如果是的那肯定不敢用了么。
现在看了这篇文章,大致明白了。

尽量找支持 asyncio 的 sdk,使用 ThreadPoolExecutor 太多的话会主线程的轮循效率

回到顶部