刷了一遍3.8文档中asyncio的部分, 总结一些基本的知识点, python后续版本中, 这个模块更新目测也会比较频繁, 虽然到3.6为止, 这个模块还不是很好用, 但从目前来看, python最新版本更新的asycnio模块已经越来越趋于稳定了.
3.8版本中有一部分的api已经宣布不推荐使用, 并会在python3.10版本中弃用, 本文章中已过滤掉将会弃用的函数,并在文章最后简要概括.
本文内容基于python3.8官方文档编写, 很多内容与python3.8官方文档相同, 英语较好的童鞋请直接阅读官方文档:
ok~ 开始扯皮~
先解释一下高级函数和低级函数:
越高级的函数封装的越完整, 越有可能被用户使用.
越低级的函数, 则越接触底层, 越有可能用户基本用不到.
1.1 协程
使用async/await语法声明的协程是编写异步应用程序的首选. 例如, 以下代码段(需要python3.7+)打印"hello", 等待1秒钟, 然后打印"hello"
>>> import asyncio
>>> async def main():...
print('hello')...
await asyncio.sleep(1)...
print('world')
>>> asyncio.run(main())
hello
world
复制代码
请注意,仅调用协程不会调度它的执行:
>>> main()
为了实际运行协程,asyncio提供了三种主要机制:
使用asyncio.run()函数来运行顶层入口函数"main"(请参照上面的例子)
等待协程. 下面的代码片段将在等待1秒钟后显示“ hello”,然后在等待另外 2秒钟后显示“ world” :
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
print(f"started at {time.strftime('%X')}")
await say_after(1, 'hello')
await say_after(2, 'world')
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
复制代码
预期的输出:
started at 17:13:52
hello world
finished at 17:13:55
复制代码asyncio.create_task() 以异步任务的形式并发运行协程
让我们修改上面的例子,并发地运行两个say_after协程:
async def main():
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'world'))
print(f"started at {time.strftime('%X')}") # 等待,直到两个任务都完成(应该花费大概2秒钟)
await task1
await task2
print(f"finished at {time.strftime('%X')}")
复制代码
注意,预期的输出现在显示代码段比以前快了1秒:
started at 17:14:32
hello world
finished at 17:14:34
复制代码
注: 协程的基本写法, 需要通过asyncio.create_task来将异步函数封装成任务, 然后通过asyncio.run来执行协程函数
1.2 异步
如果一个对象可以用在一个await表达式中,我们就说它是一个awaitable对象。许多异步api被设计成awaitable对象。
有三种主要类型的可访问对象:协程、任务和结果。
协程(conoutines):
Python协程是可等待的,因此可以从其他协程等待:
import asyncioasync def nested(): return 42async def main():
# 如果只调用“nested()”,则什么也不会发生。
# 一个协程对象被创建,但不是等待,
# 所以它根本不会运行。
nested() # 让我们现在换一种方式来等待他的执行:
print(await nested()) # 将会输出 "42".asyncio.run(main())
复制代码
这里有一个很重要的概念是,术语“协程”与两个概念密切相关:
协程函数:异步def函数;
协程对象:通过调用协同程序函数返回的对象。
异步还支持基于生成器的协同程序。
任务(task):
任务用于并发地调度协程。
当协程被包装到一个任务中,并带有asyncio.create_task()这样的函数时,协程会被自动调度且很快运行:
import asyncio
async def nested():
return 42
async def main(): # 在 "main()" 中调用nested()以尽快并发执行
task = asyncio.create_task(nested()) # "task" 现在可以被用来取消 "nested()" 或者可以简单地等待直到它完成:
await task
asyncio.run(main())
复制代码
结果(future):
Future是一个特殊的低级可访问对象,它表示异步操作的最终结果。
当一个Future对象被等待时,这意味着协程将等待,直到将来在其他地方被解析。
异步中的Future对象需要允许基于回调的代码与异步/等待一起使用。
通常不需要在应用程序级代码中创建Future的对象。
Future对象,有时通过库和一些asyncio的api暴露,是可以等待的对象:
async def main():
await function_that_returns_a_future_object()
# 这也是有效的:
await asyncio.gather(function_that_returns_a_future_object(),
some_python_coroutine())
复制代码
loop.run_in_executor()
是返回Future对象的低级函数的一个好例子。
注: 1. 一个异步函数只有被封装成awaitable或任务的形式才会被真正的执行
2. 建议将简单的逻辑通过await函数封装, 复杂的函数通过create_task函数封装
简单示例如下:
import asyncio
async def main():
result_await = await function_data("t1") # 会一直阻塞到函数结束
result_create_task = asyncio.create_task(function_data("t2")) # 继续, 可以通过下边方式或其他方式检测任务状态
while not result_create_task.done():
print("waiting")
await asyncio.sleep(1)
else:
print(result_create_task.done())
print(result_create_task.result())
asyncio.run(main())
复制代码
1.3 运行异步程序:
asyncio.run(coro, *, debug=False)
执行协程 coro并返回结果。
这个函数运行传递过来的协程参数,负责管理异步事件循环并完成异步生成器。
当另一个异步事件循环在同一线程中运行时,不能调用此函数。
如果debug设置为True,则事件循环将在调试模式下运行。
这个函数总是创建一个新的事件循环并在结束时关闭它。它应该用作异步程序的主要入口点,并且在理想情况下应该只调用一次。
示例:
async def main():
await asyncio.sleep(1)
print('hello')
asyncio.run(main())
复制代码
注: 这个函数是3.7新增加的函数, 建议作为异步程序的唯一主入口函数
1.4 创建任务
asyncio.create_task(coro, *, name=None)
将协程参数coro封装到任务中,并安排其执行。返回任务对象。
如果name不是None,则使用task .set_name()将其设置为任务的名称。
任务在get_running_loop()返回的loop中执行,如果当前线程中没有运行loop,则会引发RuntimeError。
这个函数是在Python 3.7中添加的。在Python 3.7之前,可以使用低级别的asyncio.ensure_future()函数:
在版本3.8中, 新增名称参数
async def coro(): ...# In Python 3.7+
task = asyncio.create_task(coro())... # 这适用于所有Python版本,但可读性较差
task = asyncio.ensure_future(coro())
复制代码
注: 这个函数也是3.7新增加的函数, 可以将异步函数作为参数放入此函数中封装成任务, 并通过上边的run函数来执行任务
1.5 从其他线程调度
asyncio.run_coroutine_threadsafe(coro, loop)
将协程提交给给定的事件loop。该函数是线程安全的。
返回一个concurrent.futures.Future 去接收另一个OS线程的结果。
这个函数应该从不同的OS线程调用,而不是在事件循环运行的线程中调用。
示例:
# 创建一个协程
coro = asyncio.sleep(1, result=3)
# 将协程提交给给定的loop
future = asyncio.run_coroutine_threadsafe(coro, loop)
# 等待带有可选超时参数的结果
assert future.result(timeout) == 3
复制代码
如果在协程中引发异常,将通知返回的Future。也可以用来取消事件循环中的任务:
try:
result = future.result(timeout)
except asyncio.TimeoutError:
print('协程花了太长时间,取消了任务…')
future.cancel()
except Exception as exc:
print(f'协程引发了一个异常: {exc!r}')
else:
print(f'返回的协程: {result!r}')
复制代码
与其他异步函数不同,此函数需要显式传递loop参数。
注: 可以通过该函数做到不同线程间的任务调度
1.6 自省
asyncio.current_task(loop=None)
返回当前正在运行的任务实例,如果没有任务正在运行,则返回None。
如果loop为空,则使用get_running_loop()获取当前循环。
注: python3.7中新增的api, 是非常实用的函数
asyncio.all_tasks(loop=None)¶
返回一组由loop运行的尚未完成的任务对象。
如果loop为None,则使用get_running_loop()获取当前的loop。
注: python3.7中新增的api, 是非常实用的函数
emmmm, 第一部分的主要内容就是如上了, 上述模块也是asyncio目前最新版本中最核心的功能api, 请牢记用法.
现在记录一下目前还存在, 但是预计在python3.10中删掉的api:
1. 睡眠
asyncio.sleep(delay, result=None, *, loop=None)
用来挂起当前任务, 等待其他任务
2. 并发运行的任务
asyncio.gather(*aws, loop=None, return_exceptions=False)¶
在aws中并发运行可异步的对象
3. 屏蔽取消
asyncio.shield(aw, *, loop=None)
保护一个可被取消的对象。
4. 超时
asyncio.wait_for(aw, timeout, *, loop=None)¶
等待一个可异步的程序完成并设置超时
5. 等待
asyncio.wait(aws, *, loop=None, timeout=None, return_when=ALL_COMPLETED)¶
并发地运行aws中的可异步对象,并阻塞该对象,直到返回指定的条件为止。
asyncio.as_completed(aws, *, loop=None, timeout=None)
并发地运行aws集合中的可访问对象。返回Future对象的迭代器。返回的每个Future对象中表示剩余等待对象集合的最早结果。
6. task对象
class asyncio.Task(coro, *, loop=None, name=None)
运行Python协程的类似Future的对象。不是线程安全的。
7. 基于协程的生成器
基于生成器的协程先于异步/等待语法。它们是Python生成器,使用表达式的方式来等待Future和其他协程。
如果本篇内容能够帮助到您, 希望点个关注, 共同成长~