Python中如何在Tornado框架内使用Requests等HTTP SDK实现非阻塞编程?
我知道 tornado 有 AsyncHttpClient,用它 + callback 肯定没错。
但是有的时候我用的可能是 SDK,比如 SDK 访问一个 API,一次 http 请求对面要 2 秒才能响应。
那么如果是 gevent,我理解他有猴子补丁,可以在我网络阻塞的时候,切到别的任务上工作,等到网络连接结束的时候,再切回来。
我的问题是:
-
tornado 是不是 不能在我使用基于 Http 的 SDK,或者 Requests 库的时候,自动识别我网络请求阻塞了,然后处理别的任务?
-
如果在 tornado 下不使用 AsyncHttpClient,达到我的目的呢?
搜了一圈谷歌,全是给我讲 io 多路复用和信号驱动,很少有提到这块相关的
Python中如何在Tornado框架内使用Requests等HTTP SDK实现非阻塞编程?
用异步的 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()
核心思路:
- 搞个
ThreadPoolExecutor线程池。 - 用
@run_on_executor装饰器把你那个会阻塞的requests.get()包装一下,让它跑到线程池里去。 - 在异步的
get()方法里用await调用这个包装好的函数。
这样,requests在后台线程里跑,Tornado的主事件循环就能继续处理其他请求,不会卡住。当然,这本质上还是线程,不是真正的协程异步。如果项目允许,直接换用aiohttp或Tornado自带的AsyncHTTPClient是更地道、性能也更好的选择。
总结建议:用线程池包装同步请求,或者直接换用异步HTTP客户端。
不能自动把同步变成异步处理请求,除非是 gevent 那种猴子布丁。用 asyncio.run_in_executor 是新起线程去执行
#4 感谢,对我来说明显第二种合适。
> 使用 gen.coroutine 装饰器编写异步函数,如果库本身不支持异步,那么响应任然是阻塞的。
第二种方法,ThreadPoolExecutor,起了一个另外的线程去做事情,能满足我的需求。
之前一直不敢用的原因是,不清楚这种方法,到底是不是在主线程里做的,如果是的那肯定不敢用了么。
现在看了这篇文章,大致明白了。
尽量找支持 asyncio 的 sdk,使用 ThreadPoolExecutor 太多的话会主线程的轮循效率


