From 355f2cf01b6378d1f2a4555cee95984ae22e55f6 Mon Sep 17 00:00:00 2001 From: Vishnu Vardhan P Date: Wed, 25 Feb 2026 00:18:28 +0530 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20Fix=20StreamingResponse=20async?= =?UTF-8?q?=20generator=20example=20to=20actually=20stream?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `await asyncio.sleep(0)` to the async generator in the StreamingResponse documentation example so it properly yields control back to the event loop between iterations, enabling true incremental streaming and proper task cancellation. Without an await point the async generator holds the event loop for the entire iteration, buffering the full response before sending it to the client. Also add a note admonition explaining why the await is needed and suggesting sync generators as an alternative when there are no natural await points. Closes #14680 --- docs/en/docs/advanced/custom-response.md | 14 +++++++++++++- docs_src/custom_response/tutorial007_py310.py | 3 +++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/advanced/custom-response.md b/docs/en/docs/advanced/custom-response.md index e88e958657..b7131aa0b3 100644 --- a/docs/en/docs/advanced/custom-response.md +++ b/docs/en/docs/advanced/custom-response.md @@ -167,7 +167,19 @@ You can also use the `status_code` parameter combined with the `response_class` Takes an async generator or a normal generator/iterator and streams the response body. -{* ../../docs_src/custom_response/tutorial007_py310.py hl[2,14] *} +{* ../../docs_src/custom_response/tutorial007_py310.py hl[1,4,11,17] *} + +/// note + +Notice the `await asyncio.sleep(0)` inside the async generator function. + +In asyncio, a running task only yields control back to the event loop when it hits an `await`. Without any `await` expression, the generator would hold the event loop for the entire iteration, preventing the response from being streamed chunk by chunk and also preventing proper task cancellation. + +Adding `await asyncio.sleep(0)` is a lightweight way to give the event loop a chance to send each chunk to the client and handle cancellation between iterations. + +If your generator doesn't have any natural `await` points, consider using a **normal (synchronous) generator** with `def` instead to avoid this issue. + +/// #### Using `StreamingResponse` with file-like objects { #using-streamingresponse-with-file-like-objects } diff --git a/docs_src/custom_response/tutorial007_py310.py b/docs_src/custom_response/tutorial007_py310.py index e2a53a2119..0c17ce40ee 100644 --- a/docs_src/custom_response/tutorial007_py310.py +++ b/docs_src/custom_response/tutorial007_py310.py @@ -1,3 +1,5 @@ +import asyncio + from fastapi import FastAPI from fastapi.responses import StreamingResponse @@ -6,6 +8,7 @@ app = FastAPI() async def fake_video_streamer(): for i in range(10): + await asyncio.sleep(0) yield b"some fake video bytes"