协程
笔记整理,记录一下
asyncio
是Python 3.4版本后引入的标准库,内置了对异步IO的支持。 asyncio
的编程模型就是一个消息循环。我们从asyncio
模块中直接获取一个EventLoop
的引用,然后把需要执行的协程扔到EventLoop
中执行,就实现了异步IO。
定义协程
import asyncio
@asyncio.coroutine
def aaa():
print("hello...")
print(asyncio.iscoroutinefunction(aaa)) # 判断函数是否是协程
print(asyncio.iscoroutine(aaa())) # 判断是否是协程
# 在3.5过后,我们可以使用async修饰将普通函数和生成器函数包装成异步函数和异步生成器。
async def bbb():
print("hello2...")
print(asyncio.iscoroutinefunction(bbb)) # True
print(asyncio.iscoroutine(bbb())) # True
上面的协程只是打印了一句hello...
, 现在模拟协程去执行io操作
import asyncio
@asyncio.coroutine
def ccc():
print("hello...")
asyncio.sleep(3) # 看成是一个耗时3秒的IO操作,主线程并未等待,而是去执行EventLoop中其他可以执行的coroutine了,因此可以实现并发执行
一个协程可以:
* 等待一个 future 结束
* 等待另一个协程(产生一个结果,或引发一个异常)
* 产生一个结果给正在等它的协程
* 引发一个异常给正在等它的协程
注意:
asyncio.sleep
也是一个协程。 可参见asyncio.sleep
的文档
sleep(delay, result=None, *, loop=None)
Coroutine that completes after a given time (in seconds).
运行协程
调用协程函数,协程并不会开始运行,只是返回一个协程对象。
协程的运行有两种方式:
- 在另一个已经运行的协程中用
yield from
或await
它 - 通过
ensure_future
函数计划它的执行
我们从asyncio
模块中直接获取一个EventLoop
的引用,然后把需要执行的协程扔到EventLoop
中执行。简单来说,只有 loop 运行了,协程才可能运行。下面先拿到当前线程的 loop
,然后把协程对象交给 loop.run_until_complete
。
loop = asyncio.get_event_loop()
loop.run_until_complete(aaa()) # 打印 hello...
注意:
run_until_complete
的参数是一个 future
,但是我们这里传给它的却是协程对象,之所以能这样,是因为它在内部做了检查,通过 ensure_future
函数把协程对象包装(wrap)成了 future 。下面是在这部分源码:
In [120]: loop.run_until_complete??
Signature: loop.run_until_complete(future)
Source:
def run_until_complete(self, future):
"""Run until the Future is done.
If the argument is a coroutine, it is wrapped in a Task.
WARNING: It would be disastrous to call run_until_complete()
with the same coroutine twice -- it would wrap it in two
different Tasks and that can't be good.
Return the Future's result, or raise its exception.
"""
self._check_closed()
new_task = not futures.isfuture(future)
future = tasks.ensure_future(future, loop=self)
if new_task:
# An exception is raised if the future didn't complete, so there
# is no need to log the "destroy pending task" message
future._log_destroy_pending = False
future.add_done_callback(_run_until_complete_cb)
我们也可以这样写:
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.ensure_future(aaa())) # 打印 hello...
协程去执行另一个协程
@asyncio.coroutine
def aaa():
print("hello...")
yield from bbb()
# await bbb() # 同上, 但必须要这样定义协程async def aaa(): pass 否则报语法错误
async def bbb(): # 同aaa
print("hello2...")
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.ensure_future(aaa()))
# 打印结果
hello...
hello2...
[Finished in 0.5s]
关于执行顺序:
import asyncio
async def aaa():
print("aaaa")
r = await bbb("send content")
print(r)
print("aaa end")
async def bbb(c):
print("bbbb")
print(c)
return 'response content'
def main():
print("start")
loop = asyncio.get_event_loop()
loop.run_until_complete(aaa())
print("End")
if __name__ == '__main__':
main()
# 打印结果
start
aaaa
bbbb
send content
response content
aaa end
End
[Finished in 0.5s]
添加回调
可以通过给future
添加回调方法,以便执行完Io操作, 希望得到通知
async def aaa():
print("hello...")
await bbb()
def cb(fu):
print("done")
print(fu)
loop = asyncio.get_event_loop()
fu = asyncio.ensure_future(aaa())
fu.add_done_callback(cb) # 添加回调方法
loop.run_until_complete(fu)
多个协程
async def aaa():
print("hello...")
await asyncio.sleep(3)
print("hello again...")
async def bbb():
print("hello2...")
await asyncio.sleep(3)
print("hello2 again...")
loop = asyncio.get_event_loop()
# 法一:
fu = asyncio.gather(aaa(), bbb())
# 法二:
tasks = [aaa(), bbb()]
fu = asyncio.gather(*tasks)
# 法三:
tasks = [asyncio.ensure_future(aaa()),
asyncio.ensure_future(bbb())]
fu = asyncio.gather(*tasks) # gather方法,把多个 futures 包装成单个 future
loop.run_until_complete(fu) # 两个协程是并发运行的
loop.close()
注意:
asyncio.wait()
也可以将多个协程聚合成一个future
。
具体差别可请参见 StackOverflow 的讨论:Asyncio.gather vs asyncio.wait。
参考文档:
https://segmentfault.com/a/1190000008814676
https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001432090954004980bd351f2cd4cc18c9e6c06d855c498000
https://zhuanlan.zhihu.com/p/27258289