From 07d5a0aa37562ac83101d19f1640dbba1f2557df Mon Sep 17 00:00:00 2001 From: anordin95 Date: Fri, 22 Aug 2025 12:14:58 -0700 Subject: [PATCH 1/9] - Small clarifications and phrasing improvements --- .../a-conceptual-overview-of-asyncio.rst | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/Doc/howto/a-conceptual-overview-of-asyncio.rst b/Doc/howto/a-conceptual-overview-of-asyncio.rst index d68f7cc6921fc9..16c46baa76805d 100644 --- a/Doc/howto/a-conceptual-overview-of-asyncio.rst +++ b/Doc/howto/a-conceptual-overview-of-asyncio.rst @@ -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`. @@ -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 @@ -171,14 +171,15 @@ Roughly speaking, :ref:`tasks ` 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. +Since there's only one event loop (per thread; in thread-local storage), +: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. :: @@ -251,6 +252,10 @@ different ways:: In a crucial way, the behavior of ``await`` depends on the type of object being awaited. +^^^^^^^^^^ +await task +^^^^^^^^^^ + 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. @@ -282,6 +287,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 +^^^^^^^^^^^^^^^ + **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 @@ -348,8 +357,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 data structures and work through +its processing logic to resume the next job. +That might sound minor, but in a large program with many ``await task``'s that +overhead can add up to a meaningful performance drag. ------------------------------------------------ A conceptual overview part 2: the nuts and bolts @@ -365,7 +376,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 the Python language to pass +around control. :meth:`coroutine.send(arg) ` is the method used to start or resume a coroutine. From 34f496f146d2dd60a2affa47a4a35926b1ed6cc2 Mon Sep 17 00:00:00 2001 From: anordin95 Date: Sat, 23 Aug 2025 09:48:32 -0700 Subject: [PATCH 2/9] nit --- Doc/howto/a-conceptual-overview-of-asyncio.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/howto/a-conceptual-overview-of-asyncio.rst b/Doc/howto/a-conceptual-overview-of-asyncio.rst index 16c46baa76805d..6289bbe64b9260 100644 --- a/Doc/howto/a-conceptual-overview-of-asyncio.rst +++ b/Doc/howto/a-conceptual-overview-of-asyncio.rst @@ -359,7 +359,7 @@ Each time a task is awaited, control needs to be passed all the way up the call stack to the event loop. Then, the event loop needs to manage its data structures and work through its processing logic to resume the next job. -That might sound minor, but in a large program with many ``await task``'s that +That might sound minor, but in a large program with many ``await``'s that overhead can add up to a meaningful performance drag. ------------------------------------------------ From b189b94b6f62e6d5e2b98e70e53ccfdf7bb0bf1b Mon Sep 17 00:00:00 2001 From: Alexander Nordin Date: Sat, 23 Aug 2025 15:04:19 -0700 Subject: [PATCH 3/9] Apply suggestions from code review Co-authored-by: Peter Bierma --- Doc/howto/a-conceptual-overview-of-asyncio.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/howto/a-conceptual-overview-of-asyncio.rst b/Doc/howto/a-conceptual-overview-of-asyncio.rst index 6289bbe64b9260..a612db6b120066 100644 --- a/Doc/howto/a-conceptual-overview-of-asyncio.rst +++ b/Doc/howto/a-conceptual-overview-of-asyncio.rst @@ -42,7 +42,7 @@ Event Loop ========== Everything in :mod:`!asyncio` happens relative to the event loop. -It's the star of the show, but prefers to work behind the scenes managing +It's the star of the show, but prefers to work behind the scenes, managing and coordinating resources. It's like an orchestra conductor. Some power is explicitly granted to it, but a lot of its ability to get things @@ -359,7 +359,7 @@ Each time a task is awaited, control needs to be passed all the way up the call stack to the event loop. Then, the event loop needs to manage its data structures 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 +That might sound minor, but in a large program with many ``await``s, that overhead can add up to a meaningful performance drag. ------------------------------------------------ From f97bfb64f93d12058c323aeaa53b4e84da5a3f5f Mon Sep 17 00:00:00 2001 From: anordin95 Date: Sat, 23 Aug 2025 15:21:06 -0700 Subject: [PATCH 4/9] clarify event loops when multi threading. --- Doc/howto/a-conceptual-overview-of-asyncio.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Doc/howto/a-conceptual-overview-of-asyncio.rst b/Doc/howto/a-conceptual-overview-of-asyncio.rst index a612db6b120066..3fdf5f1075f3a6 100644 --- a/Doc/howto/a-conceptual-overview-of-asyncio.rst +++ b/Doc/howto/a-conceptual-overview-of-asyncio.rst @@ -176,10 +176,11 @@ 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 (per thread; in thread-local storage), -: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. +In many applications there's only a single thread with the one event loop, +but you can have multiple threads, each with their own event loop. +Each event loop is stored in thread-local storage, making it easy for +:mod:`!asyncio` to associate tasks with the relevant loop for each thread. :: From a389c09682c2db41fba9f7720ff5a876b24bace0 Mon Sep 17 00:00:00 2001 From: anordin95 Date: Sat, 23 Aug 2025 15:22:05 -0700 Subject: [PATCH 5/9] nit --- Doc/howto/a-conceptual-overview-of-asyncio.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/howto/a-conceptual-overview-of-asyncio.rst b/Doc/howto/a-conceptual-overview-of-asyncio.rst index 3fdf5f1075f3a6..9c4a5681e89a04 100644 --- a/Doc/howto/a-conceptual-overview-of-asyncio.rst +++ b/Doc/howto/a-conceptual-overview-of-asyncio.rst @@ -360,7 +360,7 @@ Each time a task is awaited, control needs to be passed all the way up the call stack to the event loop. Then, the event loop needs to manage its data structures 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 +That might sound minor, but in a large program with many ``await``\ s, that overhead can add up to a meaningful performance drag. ------------------------------------------------ From 2831208001402f22888352bd4eb108fef3f64ab5 Mon Sep 17 00:00:00 2001 From: Alexander Nordin Date: Sat, 23 Aug 2025 20:49:47 -0700 Subject: [PATCH 6/9] Update Doc/howto/a-conceptual-overview-of-asyncio.rst Co-authored-by: Peter Bierma --- Doc/howto/a-conceptual-overview-of-asyncio.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/howto/a-conceptual-overview-of-asyncio.rst b/Doc/howto/a-conceptual-overview-of-asyncio.rst index 9c4a5681e89a04..3ba510ea821b41 100644 --- a/Doc/howto/a-conceptual-overview-of-asyncio.rst +++ b/Doc/howto/a-conceptual-overview-of-asyncio.rst @@ -377,7 +377,7 @@ and how to make your own asynchronous operators. The inner workings of coroutines ================================ -:mod:`!asyncio` leverages four components of the Python language to pass +:mod:`!asyncio` leverages four components of Python to pass around control. :meth:`coroutine.send(arg) ` is the method used to start or From 274c727dad77bd70c7328b2d583edb854408d086 Mon Sep 17 00:00:00 2001 From: anordin95 Date: Sat, 23 Aug 2025 20:51:11 -0700 Subject: [PATCH 7/9] nit --- Doc/howto/a-conceptual-overview-of-asyncio.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/howto/a-conceptual-overview-of-asyncio.rst b/Doc/howto/a-conceptual-overview-of-asyncio.rst index 3ba510ea821b41..843c2ae5218404 100644 --- a/Doc/howto/a-conceptual-overview-of-asyncio.rst +++ b/Doc/howto/a-conceptual-overview-of-asyncio.rst @@ -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 - such as when I/O operations complete or timers expire. +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 From 12d6e356dea52a64bb62071c803a80ee742d2e91 Mon Sep 17 00:00:00 2001 From: anordin95 Date: Sat, 23 Aug 2025 21:06:41 -0700 Subject: [PATCH 8/9] nit --- Doc/howto/a-conceptual-overview-of-asyncio.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/howto/a-conceptual-overview-of-asyncio.rst b/Doc/howto/a-conceptual-overview-of-asyncio.rst index 843c2ae5218404..9972dcc8454dc5 100644 --- a/Doc/howto/a-conceptual-overview-of-asyncio.rst +++ b/Doc/howto/a-conceptual-overview-of-asyncio.rst @@ -358,7 +358,7 @@ 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. -Then, the event loop needs to manage its data structures and work through +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. From 176814e0a248cdaf3da793d76f15885a51943e46 Mon Sep 17 00:00:00 2001 From: anordin95 Date: Sat, 23 Aug 2025 21:26:59 -0700 Subject: [PATCH 9/9] phrasing for threads & event loops. --- Doc/howto/a-conceptual-overview-of-asyncio.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/howto/a-conceptual-overview-of-asyncio.rst b/Doc/howto/a-conceptual-overview-of-asyncio.rst index 9972dcc8454dc5..485c8ae48186f0 100644 --- a/Doc/howto/a-conceptual-overview-of-asyncio.rst +++ b/Doc/howto/a-conceptual-overview-of-asyncio.rst @@ -177,10 +177,10 @@ 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`. :mod:`!asyncio` automatically associates tasks with the event loop for you. -In many applications there's only a single thread with the one event loop, -but you can have multiple threads, each with their own event loop. -Each event loop is stored in thread-local storage, making it easy for -:mod:`!asyncio` to associate tasks with the relevant loop for each thread. +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 +storage. ::