参考链接:
https://docs.python.org/zh-cn/3.7/library/asyncio-task.html
在Python中使用Asyncio系统(3-4)Task 和 Future - 知乎
Python:协程中 Task 和 Future 的理解及使用 - 简书
https://www.cnblogs.com/micheryu/p/15779377.html
https://www.cnblogs.com/my_life/articles/9087317.html
Python多任务—协程(asyncio详解) 一_asyncio.wait_for-CSDN博客
Python asyncio库核心源码解析_深入理解 python 异步编程 中-CSDN博客
协程及asyncio基础概念
一、
协程(coroutine)也叫微线程,是比线程更小的执行单元,是一种特殊的线程,它可以通过事件循环切换任务,比进程和线程的任务切换效率都高,这是因为进程和线程的任务切换是由操作系统进行的,而协程的任务切换是单纯由代码实现的。
二、
python实现协程主要借助于两个库: asyncio 和 gevent 。 asyncio 是在python3.4时就引入的标准库,直接内置了对协程异步IO的支持。
三、
asyncio 的编程模型本质上是一个消息循环,我们一般先定义一个协程函数, 再通过 asyncio 模块创建事件循环,最后把需要执行的协程任务(或任务列表)扔到事件循环中执行,就实现了异步IO。
四、生活类比
假设有1个洗衣房,里面有10台洗衣机,有1个洗衣工在负责这10台洗衣机。那么1个洗衣房就相当于1个进程,1个洗衣工就相当于1个线程,如果有10个洗衣工,就相当于10个线程,1个进程是可以开多线程的,这就是多线程!洗衣机洗衣服是需要等待时间的,如果有10个洗衣工,1人负责1台洗衣机,这样效率肯定会提高,但是不觉得浪费资源吗?明明1 个人能做的事,却要10个人来做,只是把衣服放进去,打开开关,就没事做了,等衣服洗好再拿出来就可以了,就算很多人来洗衣服,1个人也足以应付,开好第1台洗衣机,在等待的时候去开第2台洗衣机,再开第3台……,直到有洗衣机洗好了,就回来把衣服取出来,接着再取另1台的……(哪台先洗好就先取哪台,所以协程是无序的)。这就是计算机的协程!而洗衣机就是具体的协程函数。
五、async理解
async 用来声明一个函数为异步函数,异步函数的特点是函数能在执行的过程中挂起,去执行其他的异步函数,等到挂起条件(假设挂起条件是asyncio.sleep(5))消失后,(就是5秒后)再回来执行后续代码。
六、await理解
await会把当前协程任务挂起,并把任务控制权交给事件循环,事件循环相当于指挥者角色,指挥下一个协程任务执行。await 用来声明程序挂起,比如异步函数执行到某一步时需要等待的时间很长,就在此时将此异步函数挂起,去执行其他的异步函数。await 后面只能跟可等待对象。假设有两个异步函数a和b,异步函数a中的某一步有await b(),当程序执行到await b()时,当前异步函数a就会挂起去执行另一个异步函数b,就是从函数a内部跳出去执行函数b,当挂起条件消失后,不管函数b是否执行完,都会立马从中跳出来,回到函数a跳出位置继续执行后面的代码。如果await后面跟的函数b不是异步函数,无法在函数b执行的过程中返回,只能等函数b执行完,挂起条件才会消失。如果要在函数b执行完才返回,也就不需要用await关键字了,直接调用函数b就行,所以这就需要await后面跟的是异步函数了。在一个异步函数中,可以不止一次挂起,也就是可以用多个await。
七、[xxx = ]await awaitable/asyncio.wait(fs)
await后必须接awaitable对象(可等待对象)——实现了__await__()方法的对象,主要有三种: Coroutine对象, Task对象 和 Future对象。遇到await,其一,把当前协程任务挂起,等待直到拿到可等待对象的返回结果,才会继续执行后面的代码;其二,把任务控制权交给事件循环。
await前可设置变量接收返回值。当为单任务时,[xxx = ]await awaitable,xxx是协程任务的返回结果;当为多任务时,[xxx = ]await asyncio.wait(fs),xxx设置为done 和 pending接收已完成的协程任务集合和超时未完成的协程任务集合。
八、
(个人看法)create_task方法或ensure_future函数并不会把协程任务添加进事件循环,而是将其排入日程准备执行,因此会出现协程任务未执行完全的现象(如下方示例一);在单任务中,run_until_complete方法或run函数会把日程上的协程任务添加进事件循环;在多任务中,wait协程函数或gather函数收集日程上的协程任务,并添加进事件循环。
asyncio模块部分源码
sleep协程函数源码
'''
async def sleep(delay, result=None, *, loop=None):
"""Coroutine that completes after a given time (in seconds).""" # coroutine对象在延迟指定时间之后才执行完成
if delay <= 0:
await __sleep0()
return result
if loop is None:
loop = events.get_event_loop()
future = loop.create_future()
h = loop.call_later(delay,
futures._set_result_unless_cancelled,
future, result)
try:
return await future
finally:
h.cancel()
'''
ensure_future函数源码
'''
def ensure_future(coro_or_future, *, loop=None): # coro_or_future参数:coroutine对象或Future对象
"""Wrap a coroutine or an awaitable in a future. # 翻译:将一个coroutine对象或可等待对象包装成一个Future对象
If the argument is a Future, it is returned directly. # 翻译:如果该参数是一个Future对象,该函数会直接返回该Future对象
"""
if coroutines.iscoroutine(coro_or_future): # 判断coro_or_future是否是coroutine对象,如果是
if loop is None: # 判断是否创建了事件循环对象,如果否
loop = events.get_event_loop() # 创建一个事件循环对象
task = loop.create_task(coro_or_future) # create_task方法需传入一个coroutine对象,返回一个Task对象
if task._source_traceback:
del task._source_traceback[-1]
return task # 返回该Task对象
elif futures.isfuture(coro_or_future): # 判断coro_or_future是否是Future对象,如果是
if loop is not None and loop is not futures._get_loop(coro_or_future):
raise ValueError('loop argument must agree with Future')
return coro_or_future # 返回该Future对象
elif inspect.isawaitable(coro_or_future): # 判断coro_or_future是否是awaitable对象(可等待对象),如果是
return ensure_future(_wrap_awaitable(coro_or_future), loop=loop) # _wrap_awaitable函数会将一个awaitable对象包装成一个coroutine对象,然后递归函数重新执行上方代码
else:
raise TypeError('An asyncio.Future, a coroutine or an awaitable is '
'required')
'''
create_task方法源码
'''
def create_task(self, coro):
"""Schedule a coroutine object. # 翻译:传入一个coroutine对象
Return a task object. # 翻译:返回一个Task对象
"""
self._check_closed()
if self._task_factory is None:
task = tasks.Task(coro, loop=self)
if task._source_traceback:
del task._source_traceback[-1]
else:
task = self._task_factory(self, coro)
return task
'''
wait协程函数源码
'''
async def wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED):
"""Wait for the Futures and coroutines given by fs to complete. # 翻译:等待由fs(此处为由多个Future对象或coroutine对象组成的序列)提供的Future对象或coroutine对象执行完成
The sequence futures must not be empty. # 翻译:Future对象序列不能为空
Coroutines will be wrapped in Tasks. # 翻译:coroutine对象将被包装成Task对象
Returns two sets of Future: (done, pending). # 翻译:返回两个Future对象集合:(已执行的Future对象的集合,等待执行的Future对象的集合)
Usage: # 使用方法
done, pending = await asyncio.wait(fs)
Note: This does not raise TimeoutError! Futures that aren't done
when the timeout occurs are returned in the second set. # 翻译:当出现超时的情况时,未被执行的Future对象会被传入到第二个集合中
"""
if futures.isfuture(fs) or coroutines.iscoroutine(fs):
raise TypeError(f"expect a list of futures, not {type(fs).__name__}")
if not fs:
raise ValueError('Set of coroutines/Futures is empty.')
if return_when not in (FIRST_COMPLETED, FIRST_EXCEPTION, ALL_COMPLETED):
raise ValueError(f'Invalid return_when value: {return_when}')
if loop is None: # 判断是否创建了事件循环对象,如果否
loop = events.get_event_loop() # 创建一个事件循环对象
fs = {ensure_future(f, loop=loop) for f in set(fs)} # fs变为Future对象或Task对象的集合
return await _wait(fs, timeout, return_when, loop) # _wait协程函数返回done, pending两个集合(即已执行的Future对象的集合,等待执行的Future对象的集合),
# 注意传入的fs必须是一个Future对象的集合,而Task类继承自Future类,所以fs也可以是Task对象的集合
'''
run_until_complete方法源码
'''
def run_until_complete(self, future):
"""Run until the Future is done. # 翻译:执行事件循环直到Future对象被执行完成
If the argument is a coroutine, it is wrapped in a Task. # 翻译:如果future参数是一个coroutine对象,它会被包装成一个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. # 翻译:警告:连续两次调用run_until_complete()方法处理同一个coroutine对象会完蛋——因为该coroutine对象会被包装成两个不同的Task对象,这样不好
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)
try:
self.run_forever()
except:
if new_task and future.done() and not future.cancelled():
# The coroutine raised a BaseException. Consume the exception
# to not log a warning, the caller doesn't have access to the
# local task.
future.exception()
raise
finally:
future.remove_done_callback(_run_until_complete_cb)
if not future.done():
raise RuntimeError('Event loop stopped before Future completed.')
return future.result()
'''
run函数源码
'''
def run(main, *, debug=False):
"""Execute the coroutine and return the result. # 执行coroutine对象并返回结果
This function runs the passed coroutine, taking care of
managing the asyncio event loop and finalizing asynchronous
generators. # 该函数会执行传递过来的coroutine对象,负责管理asyncio事件循环和完成异步生成器。
This function cannot be called when another asyncio event loop is
running in the same thread. # 当另一个asyncio事件循环在同一线程中运行时,该函数无法被调用
If debug is True, the event loop will be run in debug mode.
This function always creates a new event loop and closes it at the end.
It should be used as a main entry point for asyncio programs, and should
ideally only be called once. # 该函数总是会创建一个新的事件循环,并在协程任务都结束时关闭它。
# 它应该用作asyncio程序的主要入口点,理想情况下应该只被调用一次。
Example:
async def main():
await asyncio.sleep(1)
print('hello')
asyncio.run(main())
"""
if events._get_running_loop() is not None:
raise RuntimeError(
"asyncio.run() cannot be called from a running event loop")
if not coroutines.iscoroutine(main):
raise ValueError("a coroutine was expected, got {!r}".format(main))
loop = events.new_event_loop()
try:
events.set_event_loop(loop)
loop.set_debug(debug)
return loop.run_until_complete(main)
finally:
try:
_cancel_all_tasks(loop)
loop.run_until_complete(loop.shutdown_asyncgens())
finally:
events.set_event_loop(None)
loop.close()
'''
定义协程函数、创建协程任务、执行协程任务的演变
python3.4中
python3.4中,协程函数的定义是通过 @asyncio.coroutine 和 yeild from 实现的,协程任务的手动创建是通过ensure_future函数实现,协程任务的执行是通过 run_until_complete 方法实现:
"""
单任务:虽然我们可以把协程函数直接理解为协程任务,但是严格意义上,协程函数并不能称为协程任务,协程函数到协程任务中间有一个过渡——协程任务是对协程对象的封装。
"""
import asyncio
import time
@asyncio.coroutine
def washing():
print('washer begining')
yield from asyncio.sleep(3) # 模拟IO耗时操作
print('washer finished')
coroutine = washing()
# print(coroutine) # 这里打印的结果是一个生成器对象,其实已经被装饰器装饰成协程对象
loop = asyncio.get_event_loop() # 创建一个事件循环对象
print(time.ctime())
loop.run_until_complete(coroutine) # 通过调用事件循环对象的run_until_complete方法执行协程任务,该方法传入的必须是coroutine对象(或Future对象或Task对象),此时该方法传入的是一个coroutine对象(将Task对象添加到事件循环中)
# loop.run_until_complete(asyncio.ensure_future(coroutine)) # 可运行但不采用,传入run_until_complete方法的是一个被ensure_future函数包装过的Task对象,但是run_until_complete方法本身就可实现将coroutine对象包装成Task对象,多此一举,故不采用
print(time.ctime())
# loop.run_until_complete(asyncio.wait(序列)) # 报错,执行多任务用该行代码,执行单任务用上一行代码
"""
多任务1
"""
import asyncio
import time
@asyncio.coroutine
def washing1():
print('washer1 begining')
yield from asyncio.sleep(1) # 模拟IO耗时操作
print('washer1 finished')
@asyncio.coroutine
def washing2():
print('washer2 begining')
yield from asyncio.sleep(2)
print('washer2 finished')
@asyncio.coroutine
def washing3():
print('washer3 begining')
yield from asyncio.sleep(3)
print('washer3 finished')
tasks = [washing1(), washing2(), washing3()]
loop = asyncio.get_event_loop() # 创建一个事件循环对象
print(time.ctime())
loop.run_until_complete(asyncio.wait(tasks)) # wait方法必须传入一个coroutine对象(或Future对象或Task对象)序列,此时该方法传入的是一个Task对象序列(将Task对象都添加到事件循环中)
print(time.ctime())
# loop.run_until_complete(tasks) # 报错,执行单任务用该行代码,执行多任务用上一行代码
"""
多任务2:与上方多任务1对比:
问题一:tasks的元素不同设置有什么区别:看wait协程函数源码解读
问题二:既然可以直接传递coroutine对象,为何要事先用ensure_future函数将coroutine对象包装成Task对象,不多此一举吗:
如果直接传递coroutine对象,在代码底层,首先会判断当前对象类型,由于是coroutine对象,会将其包装成Task对象,最后返回Task对象;
而如果传递的是Task对象,首先依然是判断当前对象类型,由于是Task对象,直接返回当前对象。如果当前协程任务有一万个甚至更多,那么两者之间的耗时差距就会出来了
"""
import asyncio
import time
@asyncio.coroutine
def washing1():
print('washer1 begining')
yield from asyncio.sleep(1) # 模拟IO耗时操作
print('washer1 finished')
@asyncio.coroutine
def washing2():
print('washer2 begining')
yield from asyncio.sleep(2)
print('washer2 finished')
@asyncio.coroutine
def washing3():
print('washer3 begining')
yield from asyncio.sleep(3)
print('washer3 finished')
tasks = [
asyncio.ensure_future(washing1()), # ensure_future函数传入的必须是一个coroutine对象(或Future对象或Task对象),此处该函数将一个coroutine对象包装成一个Task对象
asyncio.ensure_future(washing2()),
asyncio.ensure_future(washing3())
]
loop = asyncio.get_event_loop() # 创建一个事件循环对象
print(time.ctime())
loop.run_until_complete(asyncio.wait(tasks)) # 此时wait方法传入的是一个Task对象序列
print(time.ctime())
python3.5后
python3.5后,协程函数的定义还可以(主要)通过 async 和 await 实现的,协程任务的手动创建是通过ensure_future函数实现,协程任务的执行是通过 run_until_complete 方法实现:
"""
单任务
"""
import asyncio
import time
async def washing(): # 定义一个协程函数
print('washer begining')
await asyncio.sleep(3) # 模拟IO耗时操作
print('washer finished')
coroutine = washing()
loop = asyncio.get_event_loop() # 创建一个事件循环对象
print(time.ctime())
loop.run_until_complete(coroutine) # 此时run_until_complete方法传入的是一个coroutine对象
print(time.ctime())
"""
多任务写法1
"""
import asyncio
import time
async def washing1(): # 定义一个协程函数
print('washer1 begining')
await asyncio.sleep(1) # 模拟IO耗时操作
print('washer1 finished')
async def washing2():
print('washer2 begining')
await asyncio.sleep(2)
print('washer2 finished')
async def washing3():
print('washer3 begining')
await asyncio.sleep(3)
print('washer3 finished')
tasks = [
asyncio.ensure_future(washing1()), # 将coroutine对象转变成Task对象
asyncio.ensure_future(washing2()),
asyncio.ensure_future(washing3())
]
# tasks = [washing1(), washing2(), washing3()]
loop = asyncio.get_event_loop() # 创建一个事件循环对象
print(time.ctime())
loop.run_until_complete(asyncio.wait(tasks)) # 此时wait方法传入的是一个Task对象序列
print(time.ctime())
"""
多任务写法2
"""
import asyncio
import time
async def washing1(): # 定义一个协程函数
print('washer1 begining')
await asyncio.sleep(1) # 模拟IO耗时操作
print('washer1 finished')
async def washing2():
print('washer2 begining')
await asyncio.sleep(2)
print('washer2 finished')
async def washing3():
print('washer3 begining')
await asyncio.sleep(3)
print('washer3 finished')
async def main():
print('start main:')
tasks = [
asyncio.create_task(washing1()), # 将coroutine对象转变成Task对象
asyncio.create_task(washing2()),
asyncio.create_task(washing3())
]
await asyncio.wait(tasks) # 将Tasks对象都放进事件循环中等待执行
print('end main:')
if __name__ == '__main__':
loop = asyncio.get_event_loop() # 创建一个事件循环对象
loop.run_until_complete(main())
python3.7后
python3.7后,协程函数的定义主要通过 async 和 await 实现的,协程任务的手动创建还可以(主要)通过create_task方法实现,协程任务的执行还可以(主要)通过 asyncio.run 方法实现:
"""
单任务
"""
import asyncio
import time
async def washing(): # 定义一个协程函数
print('washer begining')
await asyncio.sleep(3) # 模拟IO耗时操作
print('washer finished')
print(time.ctime())
asyncio.run(washing()) # run方法传入的必须是一个coroutine对象
# asyncio.run(asyncio.ensure_future(washing())) # 报错
print(time.ctime())
'''
多任务
'''
import asyncio
import time
async def washing1(): # 定义一个协程函数
print('washer1 begining')
await asyncio.sleep(1) # 模拟IO耗时操作
print('washer1 finished')
async def washing2():
print('washer2 begining')
await asyncio.sleep(2)
print('washer2 finished')
async def washing3():
print('washer3 begining')
await asyncio.sleep(3)
print('washer3 finished')
async def main():
tasks = [
asyncio.create_task(washing1()), # create_task方法传入的必须是一个coroutine对象,并将coroutine对象转变成task对象
asyncio.create_task(washing2()),
asyncio.create_task(washing3())
]
# tasks = [
# asyncio.ensure_future(washing1()),
# asyncio.ensure_future(washing2()),
# asyncio.ensure_future(washing3())
# ]
# tasks = [washing1(), washing2(), washing3()]
await asyncio.wait(tasks)
if __name__ == '__main__':
print(time.ctime())
asyncio.run(main()) # run方法传入的必须是一个coroutine对象
print(time.ctime())
大同小异
示例一
import asyncio
import time
async def washing1():
print('washer1 begining')
await asyncio.sleep(1)
print('washer1 finished')
async def washing2():
print('washer2 begining')
await asyncio.sleep(2)
print('washer2 finished')
async def washing3():
print('washer3 begining')
await asyncio.sleep(3)
print('washer3 finished')
async def main():
print('start main:')
start_time = time.time()
task1 = asyncio.create_task(washing1())
task2 = asyncio.create_task(washing2())
task3 = asyncio.create_task(washing3())
await task1 # 打开下方注释,结果各不相同,此时task1、task2、task3并没有放入事件循环
print('回到main')
# await task2
# print('回到main')
# await task3
# print('回到main')
end_time = time.time()
print('end main')
print(f'总耗时:{end_time - start_time}')
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
示例二
import asyncio
# 协程与协程对象是有区别的:协程对象赋值给变量,协程名称系统可自动默认生成
def callback(future):
print(type(future)) # <class '_asyncio.Task'>
print(future) # <Task finished coro=<foo() done, defined at E: ... xxx.py:11> result=123>
print(future.result()) # 获取Future对象的返回值
print(future.get_name()) # 获取Future对象的名称
async def foo():
print("running")
return 123
async def main():
task = asyncio.create_task(foo(), name="foo") # name形参在python3.8及以上版本可用
task.add_done_callback(callback) # 添加回调函数
await task
if __name__ == '__main__':
asyncio.run(main())
示例三
gather函数和wait方法的区别:
1、wait 返回两个集合:done和pending,done为已完成的协程任务集合,pending为超时未完成的协程任务集合。需通过task.result()方法获取每个已完成的协程任务返回的结果;而gather返回一个列表,其中包含所有已完成的协程任务的结果,不需要再进行调用或其他操作就可以得到全部结果。
2、gather的返回结果顺序与协程任务传入事件循环时一致,wait的返回结果是无序的。
3、gather具有把普通协程对象包装成协程任务的能力,wait没有。wait只能接收协程任务列表做参数。
'''
协程任务执行结果并没有按任务添加的顺序返回
'''
import asyncio
async def func1(i):
print(f"协程函数{i}马上开始执行。")
await asyncio.sleep(2)
return i
async def main():
tasks = []
for i in range(1, 5):
tasks.append(asyncio.create_task(func1(i)))
done, pending = await asyncio.wait(tasks)
print('------', done, '%' * 60, pending, '------')
for task in done:
print(f"执行结果: {task.result()}")
if __name__ == '__main__':
asyncio.run(main())
'''
协程任务执行结果按任务添加的顺序返回
'''
import asyncio
async def func1(i):
print(f"协程函数{i}马上开始执行。")
await asyncio.sleep(2)
return i
async def main():
tasks = []
for i in range(1, 5):
tasks.append(func1(i))
results = await asyncio.gather(*tasks) # 返回一个列表
print('------', results, type(results)'------')
for result in results:
print(f"执行结果: {result}")
if __name__ == '__main__':
asyncio.run(main())