异步方法

Python SELF ZH
第 25 级 , 课程 2
可用

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

  • 暂停当前异步函数,直到另一个协程或异步操作完成。
  • 返回异步操作或协程的执行结果。
  • 仅可在异步函数中使用
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION