asyncio是Python3引入的异步IO功能,主要是在语言层面上解决IO阻塞的问题。
常规的用法是在主函数中运行run_until_complete这个函数。但是有的时候,代码中使用的第三方库也需要在主函数中运行自己的循环函数,比如PyQt。那么run_until_complete这个函数就无法调用了。
我尝试了下创建一个新的loop对象,在线程中调用loop对象的循环函数,解决了这个问题。
# 创建一个loop循环
new_loop = asyncio.new_event_loop()
# 定义线程函数
def Loop(loop):
while True:
try:
loop.run_forever() # 这里调用了asynicio的循环函数
except KeyboardInterrupt: # 按ctrl-c能退出
loop.close()
# 创建一个线程,在线程里面执行 run_forever
t = threading.Thread(target = Loop, args=(new_loop,))
t.daemon = True
t.start()
while True: # 这里是模拟其他库的阻塞函数
pass
在上面的代码里面,创建好了异步调用的流程,接下来是如何调用不支持asyncio的函数。拿requests来举例吧。
requests是python下常用的web请求库。我们知道http请求是很费时的,调用一次几十到几百毫秒的阻塞。
现在实现一个异步的请求:
def PostFinishTask(action_id):
asyncio.run_coroutine_threadsafe(PostFinishTaskAsync(action_id), new_loop)
用到的变量是刚才创建的new_loop对象,表示本次异步调用会在刚创建的线程里面执行,不会阻塞主线程。
async def PostFinishTaskAsync(action_id):
payload = { 'action_id': action_id } # 模拟的一些网络参数
def Do_post(): # 为了解决传参的问题,定义一个内部函数
return requests.post("https://some_call_url", data = payload)
future = new_loop.run_in_executor(None, Do_post)
response = await future # 等待调用完成
这个PostFinishTaskAsync函数才是真正干活的函数。首先加上async,然后为了解决run_in_executor无法给它回调的函数传多个参数,额外的定义了一个Do_post函数用来绕过。
经过以上改造,requests变成了非阻塞的调用了。
本文解决了两个问题,一个是如何自定义一个asyncio的loop循环。另一个是如何把阻塞函数改成非阻塞。虽然Python是一个很慢的语言,但是将IO改造成非阻塞,在一定的场合下还是能工作的不错。