You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何使用asyncio异步for循环的代码与同步代码耗时相同?

关于Asyncio异步生成器示例代码的疑惑

我正在学习O'Reilly出版的《Using Asyncio in Python》一书,书中给出了一个使用async for循环的异步生成器示例代码,运行后耗时约9秒。我认为这段代码并未真正异步执行,于是编写了同步版本的代码进行对比,结果两者耗时几乎相同。我疑惑这段示例代码是否真的是异步代码,无法理解为何书中将其称为异步代码。

书中的异步示例代码(Example 3-26)

# Example 3-26. Easier with an async generator
import asyncio
import time  # 原代码遗漏time模块导入,此处补充


# Mock Redis interface
class Redis:
    async def get(self, key):
        await asyncio.sleep(1)
        return 'value'


# Mock create_redis
# Real one: aioredis.create_redis
async def create_redis(socket):
    await asyncio.sleep(1)
    return Redis()


async def do_something_with(value):
    await asyncio.sleep(1)


# Our function is now declared with async def , making it a coroutine
# function, and since this function also contains the yield keyword, we refer
# to it as an asynchronous generator function.
async def one_at_a_time(redis, keys):
    for k in keys:
        # We don’t have to do the convoluted things necessary in the previous
        # example with self.ikeys: here, we just loop over the keys directly
        # and obtain the value...
        value = await redis.get(k)
        # ...and then yield it to the caller, just like a normal generator.
        yield value


# The main() function is identical to the version in Example 3-25.
async def main():
    redis = await create_redis(('localhost', 6379))
    keys = ['Americas', 'Africa', 'Europe', 'Asia']
    async for value in one_at_a_time(redis, keys):
        await do_something_with(value)

start = time.time()
asyncio.run(main())
end = time.time()
print(end - start)

# print result is 9.012349128723145

我编写的同步对比代码

import time


class Redis:
    def get(self, key):
        time.sleep(1)
        return 'value'

def create_redis(socket):
    time.sleep(1)
    return Redis()


def do_something_with(value):
    time.sleep(1)


def one_at_a_time(redis, keys):
    for k in keys:
        value = redis.get(k)
        yield value


# The main() function is identical to the version in Example 3-25.
def main():
    redis = create_redis(('localhost', 6379))
    keys = ['Americas', 'Africa', 'Europe', 'Asia']
    tasks = []
    for value in one_at_a_time(redis, keys):
        do_something_with(value)

start = time.time()
main()
end = time.time()
print(end-start)

# print result 9.025717973709106

问题解答

  • 这段代码确实是异步代码,但逻辑是串行执行的
    代码使用了async def、await、async for等异步语法,符合异步代码的定义,但它的执行流程是完全串行的:

    • 先等待create_redis完成(1秒)
    • 然后逐个处理每个key:等待redis.get完成(1秒)→ 等待do_something_with完成(1秒),4个key就是4×2=8秒
    • 总耗时1+8=9秒,和同步版本的耗时完全对应
  • 书中称它为异步代码的原因
    这个示例的核心是展示异步生成器的语法和用法:用async def + yield定义异步生成器,用async for遍历异步生成器产出的值。它的目的不是演示并发执行的效果,而是展示如何用更简洁的方式逐步获取异步操作的结果,替代之前需要手动实现异步迭代器的复杂写法。

  • 如果要实现并发,需要调整代码逻辑
    要让这段代码真正体现异步并发的优势,需要把多个redis.get和do_something_with任务同时提交给事件循环,比如使用asyncio.gather:

    async def main():
        redis = await create_redis(('localhost', 6379))
        keys = ['Americas', 'Africa', 'Europe', 'Asia']
        # 同时创建所有任务并等待完成
        tasks = [do_something_with(await redis.get(k)) for k in keys]
        await asyncio.gather(*tasks)
    

    这样总耗时会变成1(create_redis)+1(get的并发)+1(do的并发)=3秒,就能体现异步并发的效率提升。

内容的提问来源于stack exchange,提问作者jihyeon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 17:25:53