3.1 模块 asyncio
现在已经没人用自己创建线程来处理异步子任务了。 当然,你可以创建它们,但这种操作被认为是太底层了, 只在框架开发者中使用。而且是在无法避免的情况下。
现在潮流是
异步编程,async/await
操作符
和 协程 以及 任务。不过我们按顺序说……
一点历史
最初在 Python 中,用于解决异步编程问题的是基于生成器的协程。
然后在 Python 3.4 中出现了
asyncio
模块(有时也写作 async
IO
),实现了异步编程的机制。
在 Python 3.5 中,引入了 async/await
结构。
现在来点背景知识。首先我会简要介绍这些东西,然后再详细说。因为几乎它们都是相互关联的,要详细解释其中之一,必须参考其他。
模块 asyncio
模块 asyncio
用于编写异步程序,提供并行执行任务的可能性。
它支持异步的输入输出操作、定时器、套接字、协程的执行和多线程,运行在一个或多个事件循环中。
协程 (Coroutines)
协程 是一种
异步函数,通过关键字 async def
来定义。
协程允许
通过 await 关键字暂停它们的执行
,这
允许其他协程在此期间执行
。
import asyncio
# 定义异步函数(协程)
async def main():
print('Hello ...')
# 暂停执行 1 秒
await asyncio.sleep(1)
print('... World!')
# 在事件循环中运行异步函数 main()
asyncio.run(main())
事件循环 (Event Loop)
事件循环负责管理协程、任务和其他异步操作的执行。
调用 asyncio.run()
会启动事件循环并执行协程直到完成。
import asyncio
async def main():
print('Hello ...')
await asyncio.sleep(1)
print('... World!')
# 获取当前的事件循环
loop = asyncio.get_event_loop()
# 运行协程直到完成
loop.run_until_complete(main())
# 所有任务完成后关闭事件循环
loop.close()
任务 (Tasks)
任务允许并行运行协程。通过 asyncio.create_task()
或
asyncio.ensure_future()
创建。
import asyncio
# 定义协程,将会在延迟后执行
async def say_after(delay, what):
# 暂停执行指定时间
await asyncio.sleep(delay)
print(what)
# 主要协程
async def main():
# 创建任务以并行执行协程
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'world'))
# 等待两个任务结束
await task1
await task2
# 启动主要协程
asyncio.run(main())
期货 (Futures)
Future
对象表示异步操作的结果,将在未来可用。
例如,它们用于等待异步任务的完成。
import asyncio
# 定义协程,模拟长时间运行的任务
async def long_running_task():
print('Task started')
# 暂停执行 3 秒
await asyncio.sleep(3)
print('Task finished')
return 'Result'
# 主要协程
async def main():
# 创建期货以等待任务完成
future = asyncio.ensure_future(long_running_task())
# 等待任务完成并获取结果
result = await future
print(f'Task result: {result}')
# 启动主要协程
asyncio.run(main())
3.2 异步函数 — async def
异步函数的声明与普通函数相同,只需在 def
关键字前加上
async
。
async def 函数名(参数):
函数代码
异步函数的声明像普通函数,调用时也像普通函数, 但它返回的结果不同。如果调用异步函数,它不会返回结果,而是返回一个特殊的对象 — 协程。
你甚至可以验证一下:
import asyncio
async def main():
print("Hello World")
# 调用异步函数,返回协程
result = main()
# 检查结果类型
print(type(result)) # <class 'coroutine'>
发生了什么呢?当你用 async 标记一个函数时,它实际上添加了一个装饰器,做了大体如下的事情:
def async_decorator(func):
# 创建 Task 对象
task = Task()
# 将我们的函数 func 传递给它,以便它执行
task.target = func
# 将 task 对象添加到任务队列 — Event Loop
eventloop.add_task(task)
# 返回 task 对象
return task
而你的代码就类似于:
import asyncio
@async_decorator
def main():
print("Hello World")
result = main()
print(type(result)) # <class 'coroutine'>
此类比的意义是:
当你调用异步函数时,会创建一个特殊的 Task
对象,它会在未来执行你的
函数,也许在 0.0001 秒后,也许在 10 秒后。
这个 对象 task
在你调用异步函数时立即返回。 这就是 协程。
你的异步函数可能还没开始执行,而 task
对象(协程)你已经有了。
这个 task
(协程)有什么用呢?实际上你能做的事情不多,但可以做 3 件事:
- 等待异步函数执行完成。
- 等待异步函数完成执行,并从协程中获取函数的结果。
- 等待执行完 10(或任何数量)个异步函数。
如何做到这点,我会在下面详述。
3.3 操作符 await
大多数对协程的操作从“等待异步函数完成”开始。因此我们有一个专门的 操作符 await
。
你只需在协程前写上它:
await 协程
或者直接在调用异步函数前写上:
await 异步函数(参数)
当 Python 在代码中遇到 操作符 await
时,它会暂停当前函数的执行,并等待
协程完成执行 — 即异步函数,协程所指向的异步函数。
重要!
操作符 await
仅在 异步函数中使用,用于暂停执行直到
另一个协程或异步操作完成。
这样做是为了简化在异步函数调用之间的切换过程。这样调用
await
— 实际上是在声明“我们将在这里等待未知的时间 — 去处理其他异步函数调用。”
示例:
import asyncio
# 定义异步函数
async def async_print(text):
print(text)
# 主要异步函数
async def main():
# 使用 await 等待异步函数完成
await async_print("Hello World")
# 启动主事件循环并执行协程 main()
asyncio.run(main()) # 启动异步函数
实际上,操作符 await
的工作更巧妙 — 它还返回异步函数的执行结果,
在其上调用。
示例:
import asyncio
# 定义异步函数,加和两个数
async def async_add(a, b):
return a + b
# 主要异步函数
async def main():
# 使用 await 获取 async_add 的执行结果
sum = await async_add(100, 200)
print(sum)
# 启动主事件循环并执行协程 main()
asyncio.run(main()) # 启动异步函数
总结一下,操作符 await
:
- 暂停当前异步函数,直到另一个协程或异步操作完成。
- 返回异步操作或协程的执行结果。
- 仅可在异步函数中使用。
GO TO FULL VERSION