python 异步调用语法 async/coroutine/

异步编程是一种高效的并发编程范式,特别适用于 I/O 密集型的应用(如网络请求、文件读写、数据库操作等)。它的核心思想是:当遇到需要等待的操作时,不是让程序“干等”,而是暂停当前任务,去执行其他可以立即运行的任务,等到那个等待的操作完成后,再回来继续执行。

 Python 3.5就已经开始支持异步编程语法,异步编程的核心语法就是async/await两个关键字,主要涉及的概念就是协程(coroutine),协程就是在一个线程(thread)里通过事件循环event loop)模拟出多个线程并发的效果。

 

Python中的协程概念

在Python中,协程coroutine有两层含义:

  1. 使用async def定义的函数是一个coroutine,这个函数内部可以用await关键字。
  2. 使用async def定义的函数,调用之后返回的值,是一个coroutine对象,可以被用于await或者asyncio.run等。

我们可以看到:

  1. 第一层含义是语法层面的概念,一个函数(一段代码)由async def定义,那么它就是一个coroutine。带来的效果是,这个函数内部可以用await。那么反过来就是说,一个普通的def定义的函数,内部不能用await,否则就会触发语法错误(SyntaxError)。
  2. 第二层含义是Python解释器运行时的概念,coroutine是Python解释器里内置的一个类。当我们调用async def定义的函数时,得到的返回值的类型就是coroutine

 

在Python的异步编程中,真正并发的对象是任务(Task)。当我们对一个Task进行await的时候,event loop开始调度当前可执行的全部任务,直到被await的Task结束。

event loop: event loop事件循环是异步编程的核心引擎,负责调度和执行协程

coroutine:协程(coroutine)是Python中用于异步编程的基本构建块。它是可以暂停和恢复执行的函数。

Task:Task是事件循环中用来调度协程执行的封装。

image

概念作用生命周期创建方式
Coroutine 异步函数定义 被调用时创建,执行完销毁 async def 定义
Event Loop 调度器和执行器 程序运行期间存在 asyncio.get_event_loop()
Task 协程的包装器,可管理状态 创建后加入事件循环调度 asyncio.create_task()

 

asyncio里面,await的用法有两种:

  • await coroutine,就像普通的函数调用一样,执行coroutine对应的代码
  • await task,中断当前代码的执行,event loop开始调度任务,直到task执行结束,恢复执行当前代码。

 

1. 核心概念与关键字

async - 定义异步函数

  • 在 def 关键字前加上 async,这个函数就变成了一个“异步函数”或“协程函数”。

  • 调用异步函数时,它会立即返回一个协程对象,但函数体内的代码并不会立即执行。

  • 异步函数内部可以使用 await 关键字。

async def my_async_function():
    return "Hello, Async World!"

# 调用它不会直接执行,而是返回一个协程对象
coroutine = my_async_function()
print(coroutine) # 输出:<coroutine object my_async_function at 0x...>

await - 等待异步操作完成

  • await 后面必须跟一个可等待对象,最常见的就是另一个协程。

  • 当执行到 await 时,当前协程会暂停,并将控制权交还给事件循环。事件循环会去执行其他任务。

  • 直到 await 后面的操作完成,事件循环才会在适当的时候回来,从这个 await 之后的地方继续执行当前协程。

  • await 只能在 async 定义的函数内部使用。

async def say_after(delay, what):
    await asyncio.sleep(delay) # 模拟一个耗时的I/O操作
    print(what)

async def main():
    print("Started")
    await say_after(1, 'Hello') # 等待这个协程完成
    await say_after(1, 'World') # 再等待这个协程完成
    print("Finished")

2. 事件循环 - 异步引擎

事件循环是异步编程的“心脏”。它负责管理和调度所有协程,当某个协程因 await 而暂停时,事件循环就去找另一个可以运行的协程来执行。

在大多数情况下,你不需要直接操作事件循环,asyncio.run() 会帮你管理。

import asyncio

async def main():
    # ... 你的异步代码 ...

# 这是运行异步程序的推荐方式(Python 3.7+)
asyncio.run(main())

asyncio.run() 负责创建一个新的事件循环,运行你的主协程,并在完成后关闭循环。

3. 并发执行任务

异步的强大之处在于并发。我们可以创建多个任务,让它们在事件循环中并发执行。

asyncio.create_task()

  • 它接受一个协程,并将其“封装”成一个 Task 对象,并立即排入事件循环等待执行。

  • Task 是 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(1, 'World'))

    print("Started")

    # 等待两个任务都完成
    await task1
    await task2

    print("Finished") # 总共耗时约1秒,而不是2秒

asyncio.run(main())

输出:
Started
(等待约1秒后)
Hello
World
Finished

asyncio.gather()

  • 用于并发运行多个可等待对象,并收集它们的结果。

  • 它返回一个包含所有结果的列表(顺序与传入顺序一致)。

import asyncio

async def fetch_data(id, delay):
    print(f"Task {id} started")
    await asyncio.sleep(delay)
    print(f"Task {id} finished")
    return f"Data from {id}"

async def main():
    # 并发执行三个任务,并等待它们全部完成
    results = await asyncio.gather(
        fetch_data(1, 2),
        fetch_data(2, 1), # 这个先完成
        fetch_data(3, 3),
    )
    print("All tasks done:", results)

asyncio.run(main())

输出:

Task 1 started
Task 2 started
Task 3 started
Task 2 finished
Task 1 finished
Task 3 finished
All tasks done: ['Data from 1', 'Data from 2', 'Data from 3']

4. 可等待对象

在 Python 异步中,有三种主要类型的对象可以在 await 表达式中使用:

  1. 协程:由 async def 定义的函数返回的对象。

  2. Task:由 asyncio.create_task() 等函数创建,用于并发调度协程。

  3. Future:一个低层次的、代表异步操作最终结果的对象。Task 是 Future 的子类。通常我们不需要直接创建 Future

总结

 
概念/语法作用
async def 定义一个异步函数(协程)
await 暂停当前协程,等待一个异步操作完成
asyncio.run() 运行主异步函数(管理事件循环)
asyncio.create_task() 将协程包装成任务,并发执行
asyncio.gather() 并发运行多个任务并收集结果
asyncio.sleep() 异步的等待(替代 time.sleep)
事件循环 异步程序的调度中心

 

5. 常见陷阱与最佳实践

  1. 不要混用阻塞和非阻塞代码:

    • 在异步函数中,避免调用同步的、耗时的阻塞函数(如 time.sleep(1),同步的 requests.get)。这会阻塞整个事件循环,使并发失效。

    • 应该使用对应的异步版本(asyncio.sleep(1)aiohttp)。

  2. 记得使用 await

    • 创建了任务或协程后,如果不 await 它,它可能不会执行完成(除非你用 create_task 将其加入了事件循环)。对于需要等待结果的任务,一定要 await

  3. 理解“并发”不等于“并行”:

    • 异步是单线程的,通过任务切换实现并发。它适用于 I/O 密集型任务,但不适用于 CPU 密集型任务(因为 CPU 操作会阻塞事件循环)。对于 CPU 密集型任务,应使用 multiprocessing

  4. 使用 async with 和 async for

    • 对于支持异步上下文管理的对象(如 aiohttp.ClientSession),使用 async with

    • 对于异步可迭代对象,使用 async for

异步优势

  1. I/O 密集型任务特点:

    • 大量时间花费在等待上(网络、磁盘、数据库)

    • CPU 使用率低

    • 非常适合异步编程

  2. 异步优势:

    • 显著提升吞吐量:4个1秒的请求,同步需要4秒,异步只需要1秒

    • 更好的资源利用率:单线程处理大量并发连接

    • 响应性更好:不会因为一个慢请求阻塞其他请求

 

示例:

异步http请求

import aiohttp
import asyncio
import time

async def fetch_url(session, url, delay=1):
    """模拟获取一个带延迟的URL"""
    start_time = time.time()
    
    # 模拟访问 httpbin 的延迟端点
    async with session.get(f'https://httpbin.org/delay/{delay}') as response:
        data = await response.json()
        elapsed = time.time() - start_time
        print(f"Fetched {url} in {elapsed:.2f}s")
        return f"Data from {url}"

async def sequential_fetch():
    """顺序获取 - 同步方式"""
    async with aiohttp.ClientSession() as session:
        start_time = time.time()
        
        results = []
        for i in range(3):
            result = await fetch_url(session, f'url_{i}', delay=1)
            results.append(result)
        
        elapsed = time.time() - start_time
        print(f"Sequential fetching took {elapsed:.2f} seconds")
        return results

async def concurrent_fetch():
    """并发获取 - 异步方式"""
    async with aiohttp.ClientSession() as session:
        start_time = time.time()
        
        # 创建多个任务并发执行
        tasks = [
            fetch_url(session, 'url_1', delay=1),
            fetch_url(session, 'url_2', delay=1),
            fetch_url(session, 'url_3', delay=1),
            fetch_url(session, 'url_4', delay=2),
            fetch_url(session, 'url_5', delay=1),
        ]
        
        results = await asyncio.gather(*tasks)
        elapsed = time.time() - start_time
        print(f"Concurrent fetching took {elapsed:.2f} seconds")
        return results

async def main():
    print("=== 顺序获取 ===")
    await sequential_fetch()
    
    print("\n=== 并发获取 ===")
    await concurrent_fetch()

# 运行示例
asyncio.run(main())

=== 顺序获取 ===
Fetched url_0 in 1.42s
Fetched url_1 in 1.38s
Fetched url_2 in 1.40s
Sequential fetching took 4.22 seconds

=== 并发获取 ===
Fetched url_1 in 1.41s
Fetched url_2 in 1.41s
Fetched url_3 in 1.41s
Fetched url_5 in 1.41s
Fetched url_4 in 2.42s
Concurrent fetching took 2.43 seconds

 

aiofiles 并发读写文件

import aiofiles
import asyncio
import os
import time

async def write_file_async(filename, content):
    """异步写入文件"""
    async with aiofiles.open(filename, 'w', encoding='utf-8') as f:
        await f.write(content)
    print(f"Async wrote {filename}")

async def read_file_async(filename):
    """异步读取文件"""
    async with aiofiles.open(filename, 'r', encoding='utf-8') as f:
        content = await f.read()
    print(f"Async read {filename}, length: {len(content)}")
    return content

async def process_files_concurrently():
    """并发处理多个文件"""
    start_time = time.time()
    
    # 准备测试数据
    file_data = {
        'file1.txt': 'Hello from file 1' * 1000,
        'file2.txt': 'Hello from file 2' * 1000,
        'file3.txt': 'Hello from file 3' * 1000,
        'file4.txt': 'Hello from file 4' * 1000,
    }
    
    # 并发写入文件
    write_tasks = [
        write_file_async(filename, content)
        for filename, content in file_data.items()
    ]
    await asyncio.gather(*write_tasks)
    
    # 并发读取文件
    read_tasks = [
        read_file_async(filename)
        for filename in file_data.keys()
    ]
    results = await asyncio.gather(*read_tasks)
    
    elapsed = time.time() - start_time
    print(f"Async file processing took {elapsed:.2f} seconds")
    
    # 清理文件
    for filename in file_data.keys():
        os.remove(filename)
    
    return results

async def main():
    results = await process_files_concurrently()
    print(f"Processed {len(results)} files")

asyncio.run(main())

 

asyncpg 并发查询 PostgreSQL

import asyncio
import asyncpg
import time

async def setup_database():
    """设置测试数据库(需要已有PostgreSQL实例)"""
    conn = await asyncpg.connect(
        user='your_username',
        password='your_password',
        database='your_database',
        host='localhost'
    )
    
    # 创建测试表
    await conn.execute('''
        DROP TABLE IF EXISTS users;
        CREATE TABLE users (
            id SERIAL PRIMARY KEY,
            name TEXT,
            email TEXT
        )
    ''')
    
    # 插入测试数据
    for i in range(100):
        await conn.execute(
            "INSERT INTO users (name, email) VALUES ($1, $2)",
            f"User_{i}", f"user_{i}@example.com"
        )
    
    await conn.close()

async def query_user_count(connection, query_id):
    """查询用户数量 - 模拟复杂查询"""
    start_time = time.time()
    
    # 模拟一个耗时的查询
    count = await connection.fetchval("SELECT COUNT(*) FROM users")
    
    # 模拟一些数据处理
    await asyncio.sleep(0.1)
    
    elapsed = time.time() - start_time
    print(f"Query {query_id} completed in {elapsed:.2f}s, count: {count}")
    return count

async def concurrent_database_queries():
    """并发执行多个数据库查询"""
    # 创建数据库连接池
    pool = await asyncpg.create_pool(
        user='your_username',
        password='your_password',
        database='your_database',
        host='localhost',
        min_size=5,
        max_size=10
    )
    
    start_time = time.time()
    
    async with pool.acquire() as conn:
        # 创建多个并发查询任务
        tasks = [
            query_user_count(conn, i) 
            for i in range(10)
        ]
        
        results = await asyncio.gather(*tasks)
    
    elapsed = time.time() - start_time
    print(f"All database queries completed in {elapsed:.2f} seconds")
    
    await pool.close()
    return results

async def main():
    # 首先设置数据库(取消注释来运行)
    # await setup_database()
    
    # 然后运行并发查询
    results = await concurrent_database_queries()
    print(f"Final results: {results}")

# asyncio.run(main())

 

posted @ 2025-10-23 18:04  wangssd  阅读(26)  评论(0)    收藏  举报