以下代码来源于:https://www.pythonf.cn/read/99110
import asyncio
import time
now = lambda: time.time()
async def do_some_work(x):
print('Waiting: ', x)
await asyncio.sleep(x) # 执行sleep时,await使此协程主动让出控制权,loop调用其他协程
return 'Done after {}s'.format(x)
start = now()
coroutine = do_some_work(2)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(coroutine)
loop.run_until_complete(task)
print('Task ret: ', task.result())
print('TIME: ', now() - start)
结果:
Waiting: 2
Task ret: Done after 2s
TIME: 2.0022029876708984
分析一下代码:
async def do_some_work(x):
print('Waiting: ', x)
await asyncio.sleep(x) # 执行sleep时,await使此协程主动让出控制权,loop调用其他协程
return 'Done after {}s'.format(x)
这里通过async关键字将do_some_work变成了awaitable函数,通过await关键字在此处将协程的控制权交给event loop x秒
coroutine = do_some_work(2)
这里通过调用awaitable函数,返回coroutine对象
>>> type(coroutine)
coroutine
# 由于asyncio.wait也是awaitable函数,所以
>>> type(asyncio.wait(3))
coroutine
loop = asyncio.get_event_loop()
这里通过get_event_loop()方法获取到当前event loop,如果当前是主线程,且set_event_loop()没有调用,则该方法会创建一个event loop并设置为当前event loop
https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.get_event_loop
task = asyncio.ensure_future(coroutine)
这里将coroutine对象转为Task对象,Task是Future的子类,因而可以保存未来协程执行的结果,官方推荐Python 3.7+使用create_task()方法
https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task
loop.run_until_complete(task)
这里将任务注册到event loop中,并运行event loop
如果run_until_complete接收的是coroutine对象,则会自动将其转为Task对象,所以代码可改写为:
coroutine = do_some_work(2)
loop = asyncio.get_event_loop()
loop.run_until_complete(coroutine)
https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_until_complete
由于控制event loop过于复杂,官方更推荐在Python 3.7+中使用asyncio.run()
import asyncio
import time
now = lambda: time.time()
async def do_some_work(x):
print('Waiting: ', x)
await asyncio.sleep(x) # 执行sleep时,await使此协程主动让出控制权,loop调用其他协程
return 'Done after {}s'.format(x)
start = now()
coroutine = do_some_work(2)
result = asyncio.run(coroutine)
print('Task ret: ', result)
print('TIME: ', now() - start)
结果:
Waiting: 2
Task ret: Done after 2s
TIME: 2.0022029876708984
https://docs.python.org/3/library/asyncio-task.html#asyncio.run
如果将get_event_loop改为new_event_loop
loop = asyncio.new_event_loop()
则再次运行代码时会报错:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-1-8bdce8a449ee> in <module>
14 loop = asyncio.new_event_loop()
15 task = asyncio.ensure_future(coroutine)
---> 16 loop.run_until_complete(task)
17
18 print('Task ret: ', task.result())
/usr/local/Cellar/python@3.8/3.8.5/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/base_events.py in run_until_complete(self, future)
593
594 new_task = not futures.isfuture(future)
--> 595 future = tasks.ensure_future(future, loop=self)
596 if new_task:
597 # An exception is raised if the future didn't complete, so there
/usr/local/Cellar/python@3.8/3.8.5/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/tasks.py in ensure_future(coro_or_future, loop)
665 elif futures.isfuture(coro_or_future):
666 if loop is not None and loop is not futures._get_loop(coro_or_future):
--> 667 raise ValueError('The future belongs to a different loop than '
668 'the one specified as the loop argument')
669 return coro_or_future
ValueError: The future belongs to a different loop than the one specified as the loop argument
这是由于task并没有绑定在loop中
可以采用三种方式修改:
第一种:使用set_event_loop方法将loop绑定在当前线程
coroutine = do_some_work(2)
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
task = asyncio.ensure_future(coroutine)
loop.run_until_complete(task)
第二种:在ensure_future中使用loop参数指定task绑定的loop
coroutine = do_some_work(2)
loop = asyncio.new_event_loop()
task = asyncio.ensure_future(coroutine, loop=loop)
loop.run_until_complete(task)
第三种:使用loop的create_task方法实现将task绑定在该loop上
coroutine = do_some_work(2)
loop = asyncio.new_event_loop()
task = loop.create_task(coroutine)
loop.run_until_complete(task)
感觉写的有点乱,没办法asyncio里的知识点太零碎了,而且比较抽象,要理解它需要理解里面的几个关键知识点:
- event loop
- future
- coroutine
- async/await
关于asyncio.run
Python 3.7开始作为暂定引入该API,Python 3.8稳定,该API用来自动处理事件循环以简化代码,以下两段代码来自官方文档:
import asyncio
async def main():
await asyncio.sleep(0)
return 42
asyncio.run(main())
等价:
import asyncio
async def main():
await asyncio.sleep(0)
return 42
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
loop.run_until_complete(main())
finally:
asyncio.set_event_loop(None)
loop.close()