python异步编程

异步编程asyncio

py异步编程当中的协程、事件循环、任务的概念:

协程

协程(coroutine):由async def定义且可以暂停和恢复运行的函数,称为协程函数。协程函数在执行时并不会真正执行里面的代码,而是会返回一个协程对象(coroutine object)。当运行协程对象时才会真正执行里面的代码。

py 复制代码
import asyncio

async def coroutine_func():
    print("Coroutine started")
    await asyncio.sleep(1)
    print("Coroutine finished")

print(coroutine_func()) # <coroutine object coroutine_func at 0x000001B246121380>

await关键字作用:

  • await关键字可以暂停当前代码运行并让出控制权,当await后面的代码执行完毕时再去请求控制权并恢复运行,具有控制权的函数可以继续执行,而没有控制权的函数只能等待执行。
  • await也可以将后面的协程包装成任务被事件循环调度。
  • await会获取任务执行的结果并返回

事件循环

事件循环用于调度任务执行。事件循环的三种状态,分别是:检查协程、让出控制、等待协程

任务

任务是对协程的封装,包含协程的代码以及协程的状态(如运行或者等待状态等),当一个协程被包装成任务,就会被事件循环调度执行。

await可以将协程包装成任务

py 复制代码
async def read_file(filepath):
    print(f"Reading file...")
    await asyncio.sleep(2) # 这一段异步代码是一个任务
    print(f"File read")
    return filepath

当我们需要创建异步编程函数时,只需要实现三个步骤:

  • 定义协程函数
  • 包装协程为任务
  • 创建事件循环、将任务添加到事件循环并运行

首先定义协程函数,只需要将异步函数通过关键字async def去定义,然后将异步操作使用await去封装,await会让出当前控制权并等待当前协程执行完后再申请控制权(await后面只有接协程才会执行后面的代码,我们可以使用关键字asyncio将当前操作封装为协程)

包装协程为任务有几种方法

  • 代码段可以通过await关键字实现,可以将当前代码包装成任务
  • 通过create_task方法封装协程并立即返回一个task到事件循环内调度,后续可以使用await等待任务执行并获取结果,或者使用asyncio.gather()等方法聚合多个task执行

创建事件循环有两种方式,手动和自动创建事件循环

  • 手动创建事件循环,及将创建任务和获取结果分开进行,用于添加一些处理事件结果的操作

    py 复制代码
    async def main():
        url = 'example.com'
        filepath = 'example.txt'
    
        # 手动分步创建任务并添加到事件循环,获取任务执行结果
        task1 = asyncio.create_task(fetch_url(url)) # create_task() 将协程封装成一个任务对象,并将其添加到事件循环中
        task2 = asyncio.create_task(read_file(filepath))
    
        fetch_result = await task1 # 使用await关键字获取事件循环任务结果
        read_result = await task2
        print(fetch_result) # example.com
        print(read_result) # example.txt
        
    if __name__ == '__main__':
        start_time = perf_counter()
        asyncio.run(main()) # 执行异步事件循环入口,将协程调度到事件循环内运行,当执行结束关闭异步循环
        end_time = perf_counter()
        print(f'Time taken:{end_time - start_time} seconds') # 总共运行2s左右
  • 当我们不需要处理事件循环获取结果的逻辑时,一般采用方法直接获取事件循环结果即可.

    • asyncio.gather() 等待所有任务执行完后,返回结果列表
    • asyncio.as_completed() 当一有任务执行完,优先返回,返回的结果是一个迭代器
    • 以上两个函数可以传入协程或者任务
    py 复制代码
    async def main():
        url = 'example.com'
        filepath = 'example.txt'
    
        # 自动创建任务并添加到事件循环,获取任务执行结果
        results = asyncio.gather(fetch_url(url), read_file(filepath)) # 等待所有任务执行完后,返回结果列表
        print(await results) # ['example.com', 'example.txt']
    
        results2 = asyncio.as_completed([fetch_url(url), read_file(filepath)]) # 当一有任务执行完,先返回结果,返回的结果是一个迭代器
        for result in results2:
            print(await result) # example.txt example.com
            
    if __name__ == '__main__':
        start_time = perf_counter()
        asyncio.run(main()) # 执行异步事件循环入口,将协程调度到事件循环内运行,当执行结束关闭异步循环
        end_time = perf_counter()
        print(f'Time taken:{end_time - start_time} seconds') # 总共运行2s左右
  • 我们也可以直接获取事件循环对象,对事件循环做一些操作,即使用loop = asyncio.get_event_loop()获取事件循环对象

    py 复制代码
    loop = asyncio.get_event_loop() # 我们拿到事件循环对象后可以对事件循环做一些自定义的配置操作等

同步函数添加事件循环(也称阻塞函数)

已知当我们需要异步处理函数时,需要将同步函数定义为协程,但如果我们不想重构函数时,可以使用to_thread方法使用多线程处理同步函数。

  • to_thread(func, args) 将同步函数使用多线程执行,传入两个参数,第一个是同步函数,后面接函数的参数
py 复制代码
# 将同步函数使用多线程转为异步处理
    results2 = asyncio.as_completed([fetch_url(url), read_file(filepath), asyncio.to_thread(foo, 1, 2, 3)]) # to_thread将同步函数使用多线程执行,传入两个参数,第一个是同步函数,后面接函数的参数
    for result in results2:
        print(await result) # (1, 2, 3), example.com, example.txt # 因为1,2,3执行比较快所以先返回

if __name__ == '__main__':
    start_time = perf_counter()
    asyncio.run(main()) # 执行异步事件循环入口,将协程调度到事件循环内运行,当执行结束关闭异步循环
    end_time = perf_counter()
    print(f'Time taken:{end_time - start_time} seconds') # 总共运行2s左右

或者将同步函数添加到事件循环内异步执行,使用loop.run_in_executor(executor, lambda func)实现

  • executor执行器 为None表示使用默认的线程池
  • lambda func 表示需要执行的函数调用
py 复制代码
loop = asyncio.get_event_loop()
await loop.run_in_executor(executor, lambda 阻塞函数) # # 在异步线程池中执行同步命令
相关推荐
郝学胜-神的一滴2 小时前
现代C++ Lambda表达式:最佳实践、深入理解和资源推荐
开发语言·c++·程序人生·软件工程
一口面条一口蒜3 小时前
R语言中的S3 泛型与方法
开发语言·r语言
_OP_CHEN3 小时前
C++基础:(五)类和对象(下)—— static、友元和内部类
开发语言·c++·构造函数·static成员·友元·匿名对象·编译器优化
databook3 小时前
Manim实现闪电特效
后端·python·动效
yongui478343 小时前
基于MATLAB的8QAM调制解调仿真与BER性能分析
开发语言·matlab
Blossom.1183 小时前
AI“点亮”萤火虫:边缘机器学习让微光成像走进4K时代
人工智能·pytorch·python·深度学习·数码相机·opencv·机器学习
早日退休!!!3 小时前
C 内存布局
c语言·开发语言
linuxoffer4 小时前
composer 安装与开启PHP扩展支持
开发语言·php·composer