Skip to content

gh-138072: Small clarifications and phrasing improvements to asyncio HOWTO #138073

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 24 additions & 11 deletions Doc/howto/a-conceptual-overview-of-asyncio.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ Event Loop
==========

Everything in :mod:`!asyncio` happens relative to the event loop.
It's the star of the show.
It's the star of the show, but prefers to work behind the scenes, managing
and coordinating resources.
It's like an orchestra conductor.
It's behind the scenes managing resources.
Some power is explicitly granted to it, but a lot of its ability to get things
done comes from the respect and cooperation of its worker bees.
done comes from the respect and cooperation of its band members.

In more technical terms, the event loop contains a collection of jobs to be run.
Some jobs are added directly by you, and some indirectly by :mod:`!asyncio`.
Expand All @@ -60,7 +60,7 @@ This process repeats indefinitely with the event loop cycling endlessly
onwards.
If there are no more jobs pending execution, the event loop is smart enough to
rest and avoid needlessly wasting CPU cycles, and will come back when there's
more work to be done.
more work to be done, such as when I/O operations complete or timers expire.

Effective execution relies on jobs sharing well and cooperating; a greedy job
could hog control and leave the other jobs to starve, rendering the overall
Expand Down Expand Up @@ -171,14 +171,16 @@ Roughly speaking, :ref:`tasks <asyncio-task-obj>` are coroutines (not coroutine
functions) tied to an event loop.
A task also maintains a list of callback functions whose importance will become
clear in a moment when we discuss :keyword:`await`.
The recommended way to create tasks is via :func:`asyncio.create_task`.

Creating a task automatically schedules it for execution (by adding a
callback to run it in the event loop's to-do list, that is, collection of jobs).
The recommended way to create tasks is via :func:`asyncio.create_task`.

Since there's only one event loop (in each thread), :mod:`!asyncio` takes care of
associating the task with the event loop for you. As such, there's no need
to specify the event loop.
:mod:`!asyncio` automatically associates tasks with the event loop for you.
Typically there's only one event loop, so that's quite straightforward.
It's uncommon, but some applications use multithreading and :mod:`!asyncio`
together, where there's one event loop per thread, stored in thread-local
Copy link
Contributor

@kumaraditya303 kumaraditya303 Aug 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"stored in thread-local" is unnecesary implemenetation detail for the reader, just per thread seems sufficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See discussion here: #138073 (comment)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also be OK with just "per thread". But, I don't totally understand the rationale to clarify thread-locality here -- what was wrong with the original wording?

storage.

::

Expand Down Expand Up @@ -251,6 +253,10 @@ different ways::
In a crucial way, the behavior of ``await`` depends on the type of object
being awaited.

^^^^^^^^^^
await task
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
await task
Awaiting Tasks

Copy link
Contributor Author

@anordin95 anordin95 Aug 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the original for two reasons.

The titles come from the references made in the broader await section intro.

And they fit more naturally in the broader table of contents:

image

^^^^^^^^^^

Awaiting a task will cede control from the current task or coroutine to
the event loop.
In the process of relinquishing control, a few important things happen.
Expand Down Expand Up @@ -282,6 +288,10 @@ This is a basic, yet reliable mental model.
In practice, the control handoffs are slightly more complex, but not by much.
In part 2, we'll walk through the details that make this possible.

^^^^^^^^^^^^^^^
await coroutine
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
await coroutine
Awaiting Coroutines

^^^^^^^^^^^^^^^

**Unlike tasks, awaiting a coroutine does not hand control back to the event
loop!**
Wrapping a coroutine in a task first, then awaiting that would cede
Expand Down Expand Up @@ -348,8 +358,10 @@ The design intentionally trades off some conceptual clarity around usage of
``await`` for improved performance.
Each time a task is awaited, control needs to be passed all the way up the
call stack to the event loop.
That might sound minor, but in a large program with many ``await``'s and a deep
callstack that overhead can add up to a meaningful performance drag.
Then, the event loop needs to manage its internal state and work through
its processing logic to resume the next job.
That might sound minor, but in a large program with many ``await``\ s, that
overhead can add up to a meaningful performance drag.

------------------------------------------------
A conceptual overview part 2: the nuts and bolts
Expand All @@ -365,7 +377,8 @@ and how to make your own asynchronous operators.
The inner workings of coroutines
================================

:mod:`!asyncio` leverages four components to pass around control.
:mod:`!asyncio` leverages four components of Python to pass
around control.

:meth:`coroutine.send(arg) <generator.send>` is the method used to start or
resume a coroutine.
Expand Down
Loading