关于 asyncio 模块这里不做详细的介绍,如果你还不认识他,请前往 链接 自己查询吧。
我这里就假设大家都已经了解了 asyncio 这个异步io 模块的基本用法。
背景:
最近我刚刚了解到这个新的异步io 的模块,之前我只是知道 gevent。在学习这个模块的时候我发现在创建任务时可以手动的给这个任务绑定一个回调函数,而不用在自己去实现这个功能。可以说是非常的方便了。
简单的创建任务的代码如下:
# -*- coding:utf-8 -*-
import asyncio
async def async_func():
pass
def demo_clall_back_func(task):
pass
task = asyncio.ensure_future(async_func())
task.add_done_callback(demo_clall_back_func)
创建任务以及绑定任务回调的 demo 就是这样的。(async 函数内部请自行脑补一下。)
此时我就想到了一个问题: 如果现在我的 async_func 函数内是异步获取网页,demo_clall_back_func 函数的功能是把数据做持久化(例如:写入文件中)。
- 那么是谁回来执行这个回调函数那?主进程?线程?还是让任务绑定的协程?
- 如果是前两个那么就会变成一个同步的代码,可不可以把这部分代码做成异步的那?
我带着这个问题,问了我的度娘,我得到的回答是:
-
如果是进程调用,回调函数就返回到调用的进程中执行。
-
如果是线程调用,回调函数就返回调用的线程中执行。如果是多个线程的话,那个线程 空闲那个线程执行。
# -*- coding:utf-8 -*- import asyncio import time async def test_func(n): print(f'test function {n} start !') await asyncio.sleep(2) print(f'test function {n} end !') return n def test_call_back(task): print(f'test_call_back { task.result() } start !') time.sleep(2) # 模拟io阻塞 print(f'test_call_back { task.result() } end !') pool = ThreadPoolExecutor(5) tasks = [] for i in range(3): task = asyncio.ensure_future(test_func(i)) task.add_done_callback(test_call_back) tasks.append(task) loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks))
通过这个 demo 来验证回答一,执行后发现确实回调函数为同步执行。
# -*- coding:utf-8 -*- import asyncio import time from concurrent.futures import ThreadPoolExecutor async def test_func(n): print(f'test function {n} start !') await asyncio.sleep(2) print(f'test function {n} end !') return n def test_call_back(task): print(f'test_call_back { task.result() } start !') time.sleep(2) print(f'test_call_back { task.result() } end !') s = time.time() pool = ThreadPoolExecutor(5) tasks = [] for i in range(3): task = asyncio.ensure_future(test_func(i)) task.add_done_callback(test_call_back) tasks.append(task) loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks)) pool.submit(loop.run_until_complete(asyncio.wait(tasks))) print(time.time() - s )
通过这个demo 来验证回答2 ,看着好像合理,但是这里只用到了一个线程,所以和进程的效果是一样的。
而且我查了一下资料,asyncio 好像都是单线程里面使用的,并不能利用多线程调用回调,但是 它有一个方法 run_coroutine_threadsafe() 可以用来做线程间的通信。
那么问题就又回到了原点,怎么才能异步的执行回调那? 我有一次的这样询问 “度娘” 。我得到的回到也不尽如人意,有一些大神封装好了的包,可以做异步的读写,但是因为没有看懂源码,所以放弃了使用。但是大致的思想可以得到,就是在回调函数里面封装线程,示例如下:
# -*- coding:utf-8 -*-
import asyncio
import time
from concurrent.futures import ThreadPoolExecutor
async def test_func(n):
print(f'test function {n} start !')
await asyncio.sleep(2)
print(f'test function {n} end !')
return n
def test_call_back(task):
print(f'test_call_back { task.result() } start !')
pool.submit(time.sleep, 2)
print(f'test_call_back { task.result() } end !')
s = time.time()
pool = ThreadPoolExecutor(5)
tasks = []
for i in range(3):
task = asyncio.ensure_future(test_func(i))
task.add_done_callback(test_call_back)
tasks.append(task)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
pool.submit(loop.run_until_complete(asyncio.wait(tasks)))
print(time.time() - s)
改动的地方很小,就是在造成阻塞的地方从新封装一个函数,然后放到线程池中去执行。这样就可以基本做到了异步的回调。
但是还有没有更优的方法那? 是不是可以用 async 这样的异步函数来执行那?
理论上是可以行的,类比一下线程就知道了,但是 asyncio 模块中并没有提供给我们一个异步操作文件的方法,如果想要使用协程操作文件,提高速度的话,需要自己用协程封装一个,因为我能力有限没有办法实现,那么情歌文大神加油吧。