gh-138072: Fix typos and grammatical errors and improve clarity in asyncio howto document (#138895)

This commit is contained in:
Morteza24 2025-10-14 11:49:35 +03:30 committed by GitHub
parent 7caa591bb9
commit 362fd59dc8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -9,12 +9,11 @@ model of how :mod:`asyncio` fundamentally works, helping you understand the
how and why behind the recommended patterns. how and why behind the recommended patterns.
You might be curious about some key :mod:`!asyncio` concepts. You might be curious about some key :mod:`!asyncio` concepts.
You'll be comfortably able to answer these questions by the end of this By the end of this article, you'll be able to comfortably answer these questions:
article:
- What's happening behind the scenes when an object is awaited? - What's happening behind the scenes when an object is awaited?
- How does :mod:`!asyncio` differentiate between a task which doesn't need - How does :mod:`!asyncio` differentiate between a task which doesn't need
CPU-time (such as a network request or file read) as opposed to a task that CPU time (such as a network request or file read) as opposed to a task that
does (such as computing n-factorial)? does (such as computing n-factorial)?
- How to write an asynchronous variant of an operation, such as - How to write an asynchronous variant of an operation, such as
an async sleep or database request. an async sleep or database request.
@ -35,7 +34,7 @@ A conceptual overview part 1: the high-level
-------------------------------------------- --------------------------------------------
In part 1, we'll cover the main, high-level building blocks of :mod:`!asyncio`: In part 1, we'll cover the main, high-level building blocks of :mod:`!asyncio`:
the event loop, coroutine functions, coroutine objects, tasks and ``await``. the event loop, coroutine functions, coroutine objects, tasks, and ``await``.
========== ==========
Event Loop Event Loop
@ -56,7 +55,7 @@ Once it pauses or completes, it returns control to the event loop.
The event loop will then select another job from its pool and invoke it. The event loop will then select another job from its pool and invoke it.
You can *roughly* think of the collection of jobs as a queue: jobs are added and You can *roughly* think of the collection of jobs as a queue: jobs are added and
then processed one at a time, generally (but not always) in order. then processed one at a time, generally (but not always) in order.
This process repeats indefinitely with the event loop cycling endlessly This process repeats indefinitely, with the event loop cycling endlessly
onwards. onwards.
If there are no more jobs pending execution, the event loop is smart enough to 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 rest and avoid needlessly wasting CPU cycles, and will come back when there's
@ -276,7 +275,7 @@ in this case, a call to resume ``plant_a_tree()``.
Generally speaking, when the awaited task finishes (``dig_the_hole_task``), Generally speaking, when the awaited task finishes (``dig_the_hole_task``),
the original task or coroutine (``plant_a_tree()``) is added back to the event the original task or coroutine (``plant_a_tree()``) is added back to the event
loops to-do list to be resumed. loop's to-do list to be resumed.
This is a basic, yet reliable mental model. This is a basic, yet reliable mental model.
In practice, the control handoffs are slightly more complex, but not by much. In practice, the control handoffs are slightly more complex, but not by much.
@ -310,7 +309,7 @@ Consider this program::
The first statement in the coroutine ``main()`` creates ``task_b`` and schedules The first statement in the coroutine ``main()`` creates ``task_b`` and schedules
it for execution via the event loop. it for execution via the event loop.
Then, ``coro_a()`` is repeatedly awaited. Control never cedes to the Then, ``coro_a()`` is repeatedly awaited. Control never cedes to the
event loop which is why we see the output of all three ``coro_a()`` event loop, which is why we see the output of all three ``coro_a()``
invocations before ``coro_b()``'s output: invocations before ``coro_b()``'s output:
.. code-block:: none .. code-block:: none
@ -338,8 +337,8 @@ This behavior of ``await coroutine`` can trip a lot of people up!
That example highlights how using only ``await coroutine`` could That example highlights how using only ``await coroutine`` could
unintentionally hog control from other tasks and effectively stall the event unintentionally hog control from other tasks and effectively stall the event
loop. loop.
:func:`asyncio.run` can help you detect such occurences via the :func:`asyncio.run` can help you detect such occurrences via the
``debug=True`` flag which accordingly enables ``debug=True`` flag, which enables
:ref:`debug mode <asyncio-debug-mode>`. :ref:`debug mode <asyncio-debug-mode>`.
Among other things, it will log any coroutines that monopolize execution for Among other things, it will log any coroutines that monopolize execution for
100ms or longer. 100ms or longer.
@ -348,8 +347,8 @@ The design intentionally trades off some conceptual clarity around usage of
``await`` for improved performance. ``await`` for improved performance.
Each time a task is awaited, control needs to be passed all the way up the Each time a task is awaited, control needs to be passed all the way up the
call stack to the event loop. call stack to the event loop.
That might sound minor, but in a large program with many ``await``'s and a deep That might sound minor, but in a large program with many ``await`` statements and a deep
callstack that overhead can add up to a meaningful performance drag. call stack, that overhead can add up to a meaningful performance drag.
------------------------------------------------ ------------------------------------------------
A conceptual overview part 2: the nuts and bolts A conceptual overview part 2: the nuts and bolts
@ -372,7 +371,7 @@ resume a coroutine.
If the coroutine was paused and is now being resumed, the argument ``arg`` If the coroutine was paused and is now being resumed, the argument ``arg``
will be sent in as the return value of the ``yield`` statement which originally will be sent in as the return value of the ``yield`` statement which originally
paused it. paused it.
If the coroutine is being used for the first time (as opposed to being resumed) If the coroutine is being used for the first time (as opposed to being resumed),
``arg`` must be ``None``. ``arg`` must be ``None``.
.. code-block:: .. code-block::
@ -403,14 +402,14 @@ If the coroutine is being used for the first time (as opposed to being resumed)
returned_value = e.value returned_value = e.value
print(f"Coroutine main() finished and provided value: {returned_value}.") print(f"Coroutine main() finished and provided value: {returned_value}.")
:ref:`yield <yieldexpr>`, like usual, pauses execution and returns control :ref:`yield <yieldexpr>`, as usual, pauses execution and returns control
to the caller. to the caller.
In the example above, the ``yield``, on line 3, is called by In the example above, the ``yield``, on line 3, is called by
``... = await rock`` on line 11. ``... = await rock`` on line 11.
More broadly speaking, ``await`` calls the :meth:`~object.__await__` method of More broadly speaking, ``await`` calls the :meth:`~object.__await__` method of
the given object. the given object.
``await`` also does one more very special thing: it propagates (or "passes ``await`` also does one more very special thing: it propagates (or "passes
along") any ``yield``\ s it receives up the call-chain. along") any ``yield``\ s it receives up the call chain.
In this case, that's back to ``... = coroutine.send(None)`` on line 16. In this case, that's back to ``... = coroutine.send(None)`` on line 16.
The coroutine is resumed via the ``coroutine.send(42)`` call on line 21. The coroutine is resumed via the ``coroutine.send(42)`` call on line 21.
@ -462,12 +461,12 @@ computation's status and result.
The term is a nod to the idea of something still to come or not yet happened, The term is a nod to the idea of something still to come or not yet happened,
and the object is a way to keep an eye on that something. and the object is a way to keep an eye on that something.
A future has a few important attributes. One is its state which can be either A future has a few important attributes. One is its state, which can be either
"pending", "cancelled" or "done". "pending", "cancelled", or "done".
Another is its result, which is set when the state transitions to done. Another is its result, which is set when the state transitions to done.
Unlike a coroutine, a future does not represent the actual computation to be Unlike a coroutine, a future does not represent the actual computation to be
done; instead, it represents the status and result of that computation, kind of done; instead, it represents the status and result of that computation, kind of
like a status light (red, yellow or green) or indicator. like a status light (red, yellow, or green) or indicator.
:class:`asyncio.Task` subclasses :class:`asyncio.Future` in order to gain :class:`asyncio.Task` subclasses :class:`asyncio.Future` in order to gain
these various capabilities. these various capabilities.
@ -490,8 +489,8 @@ We'll go through an example of how you could leverage a future to create your
own variant of asynchronous sleep (``async_sleep``) which mimics own variant of asynchronous sleep (``async_sleep``) which mimics
:func:`asyncio.sleep`. :func:`asyncio.sleep`.
This snippet registers a few tasks with the event loop and then awaits a This snippet registers a few tasks with the event loop and then awaits the task
coroutine wrapped in a task: ``async_sleep(3)``. created by ``asyncio.create_task``, which wraps the ``async_sleep(3)`` coroutine.
We want that task to finish only after three seconds have elapsed, but without We want that task to finish only after three seconds have elapsed, but without
preventing other tasks from running. preventing other tasks from running.
@ -540,8 +539,8 @@ will monitor how much time has elapsed and, accordingly, call
# Block until the future is marked as done. # Block until the future is marked as done.
await future await future
Below, we'll use a rather bare object, ``YieldToEventLoop()``, to ``yield`` Below, we use a rather bare ``YieldToEventLoop()`` object to ``yield``
from ``__await__`` in order to cede control to the event loop. from its ``__await__`` method, ceding control to the event loop.
This is effectively the same as calling ``asyncio.sleep(0)``, but this approach This is effectively the same as calling ``asyncio.sleep(0)``, but this approach
offers more clarity, not to mention it's somewhat cheating to use offers more clarity, not to mention it's somewhat cheating to use
``asyncio.sleep`` when showcasing how to implement it! ``asyncio.sleep`` when showcasing how to implement it!
@ -552,13 +551,13 @@ The ``watcher_task``, which runs the coroutine ``_sleep_watcher(...)``, will
be invoked once per full cycle of the event loop. be invoked once per full cycle of the event loop.
On each resumption, it'll check the time and if not enough has elapsed, then On each resumption, it'll check the time and if not enough has elapsed, then
it'll pause once again and hand control back to the event loop. it'll pause once again and hand control back to the event loop.
Eventually, enough time will have elapsed, and ``_sleep_watcher(...)`` will Once enough time has elapsed, ``_sleep_watcher(...)``
mark the future as done, and then itself finish too by breaking out of the marks the future as done and completes by exiting its
infinite ``while`` loop. infinite ``while`` loop.
Given this helper task is only invoked once per cycle of the event loop, Given this helper task is only invoked once per cycle of the event loop,
you'd be correct to note that this asynchronous sleep will sleep *at least* you'd be correct to note that this asynchronous sleep will sleep *at least*
three seconds, rather than exactly three seconds. three seconds, rather than exactly three seconds.
Note this is also of true of ``asyncio.sleep``. Note this is also true of ``asyncio.sleep``.
:: ::
@ -601,6 +600,6 @@ For reference, you could implement it without futures, like so::
else: else:
await YieldToEventLoop() await YieldToEventLoop()
But, that's all for now. Hopefully you're ready to more confidently dive into But that's all for now. Hopefully you're ready to more confidently dive into
some async programming or check out advanced topics in the some async programming or check out advanced topics in the
:mod:`rest of the documentation <asyncio>`. :mod:`rest of the documentation <asyncio>`.