You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

为何在返回的元组中使用await会导致代码运行变慢?

Why does moving await response.text() into the return tuple slow down my async function?

Great question! Let's break down why this seemingly tiny change caused such a big performance hit.

First, let's recap your code changes to make sure we're on the same page:

Original (fast) code:

async def foo(page_url: str):
    async with ClientSession() as session:
        async with session.get(page_url) as response:
            if response.status == 200:
                page = await response.text()
                return (page_url, page)

Modified (slow) code:

async def foo(page_url: str):
    async with ClientSession() as session:
        async with session.get(page_url) as response:
            if response.status == 200:
                return (page_url, await response.text())

At first glance, these look functionally identical—both read the response text and return it paired with the URL. But the difference lies in when the async context manager cleans up the response, and how that interacts with the timing of your await operation.

The Root Cause

When you use async with session.get(...) as response, the context manager is responsible for properly closing the response object (and releasing the underlying TCP connection back to the pool, if applicable) once the block exits.

Let's walk through the execution flow for both versions:

  1. Original code:

    • You first await response.text() while still inside the async with block: this fully reads and decodes the response body while the response is active.
    • You store the result in the page variable, then return the tuple immediately.
    • The async with block exits right after the return, triggering immediate cleanup of the response and fast release of the TCP connection.
  2. Modified code:

    • The await response.text() is part of the return expression. Python does evaluate this while still inside the async with block, but the timing of cleanup shifts slightly.
    • Resolving the await as part of the return process adds a tiny per-call overhead, but more critically: the cleanup of the response (and connection release) gets delayed just enough to disrupt resource efficiency. Even though you're creating a new ClientSession every time (which is already inefficient), this split-second delay can cause subsequent requests to wait longer for available connections, especially when running many requests in parallel.

Wait, but creating a new ClientSession per request is already a performance anti-pattern! Sessions are designed to be long-lived and reuse TCP connections, eliminating the overhead of establishing a new connection for every request. The original code worked "okay" because the immediate return after storing page let resources clean up faster—while the modified code's delayed cleanup creates a bottleneck as requests pile up.

A Quick Fix (That Also Boosts Performance)

The biggest win here will come from reusing your ClientSession instead of creating a new one per call. This alone will likely fix the slowdown, regardless of where you place the await.

Here's how to adjust your code properly:

async def foo(session: ClientSession, page_url: str):
    async with session.get(page_url) as response:
        if response.status == 200:
            return (page_url, await response.text())

# In your main execution code:
async def main():
    async with ClientSession() as session:
        # Run multiple requests with the same session (connection pooling works!)
        results = await asyncio.gather(
            foo(session, "https://example.com"),
            foo(session, "https://example.org"),
            # Add more URLs here
        )

Even with a reused session, keeping the await response.text() separate from the return expression can help maintain predictable cleanup timing, but the session reuse is the real game-changer.

Summary

  • Moving await response.text() into the return tuple creates subtle timing shifts in how the response context manager cleans up resources, leading to slower connection release.
  • The most impactful fix is reusing a single ClientSession across requests—this eliminates the overhead of establishing new TCP connections for every call.
  • Even with a reused session, reading the response body into a variable before returning helps keep cleanup predictable and efficient.

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

火山引擎 最新活动