Skip to content

Commit 8fca2e3

Browse files
committed
- Variety of style & grammar improvements thanks to ZeroIntensity's comments.
1 parent a730bd3 commit 8fca2e3

File tree

1 file changed

+52
-46
lines changed

1 file changed

+52
-46
lines changed

Doc/howto/a-conceptual-overview-of-asyncio.rst

Lines changed: 52 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,50 +6,50 @@ A Conceptual Overview of asyncio
66

77
:Author: Alexander Nordin
88

9-
This article seeks to help you build a sturdy mental model of how asyncio
10-
fundamentally works.
11-
Something that will help you understand the how and why behind the recommended
12-
patterns.
9+
This article seeks to help you build a sturdy mental model of how ``asyncio``
10+
fundamentally works, helping you understand the how and why behind the
11+
recommended patterns.
1312

14-
During my own asyncio learning process, a few aspects particually drove my
13+
During my own ``asyncio`` learning process, a few aspects particually drove my
1514
curiosity (read: drove me nuts).
1615
You should be able to comfortably answer all these questions by the end
1716
of this article.
1817

19-
- What's roughly happening behind the scenes when an object is ``await``\ ed?
20-
- How does asyncio differentiate between a task which doesn't need CPU-time
21-
to make progress towards completion, for example, a network request or file
22-
read as opposed to a task that does need CPU-time to make progress, like
23-
computing n-factorial?
18+
- What's happening behind the scenes when an object is ``await``\ ed?
19+
- How does ``asyncio`` differentiate between a task which doesn't need CPU-time
20+
(such as a network request or file read) as opposed to a task that does
21+
(such as computing n-factorial)?
2422
- How would I go about writing my own asynchronous variant of some operation?
2523
Something like an async sleep, database request, and so on.
2624

2725
--------------------------------------------
2826
A conceptual overview part 1: the high-level
2927
--------------------------------------------
3028

31-
In part 1, we'll cover the main, high-level building blocks of asyncio: the
32-
event loop, coroutine functions, coroutine objects, tasks and ``await``.
29+
In part 1, we'll cover the main, high-level building blocks of ``asyncio``:
30+
the event loop, coroutine functions, coroutine objects, tasks and ``await``.
3331

3432
==========
3533
Event Loop
3634
==========
3735

38-
Everything in asyncio happens relative to the event loop.
36+
Everything in ``asyncio`` happens relative to the event loop.
3937
It's the star of the show.
4038
It's kind of like an orchestra conductor or military general.
4139
It's behind the scenes managing resources.
4240
Some power is explicitly granted to it, but a lot of its ability to get things
4341
done comes from the respect and cooperation of its subordinates.
4442

45-
In more technical terms, the event loop contains a queue of tasks to be run.
46-
Some tasks are added directly by you, and some indirectly by asyncio.
47-
The event loop pops a task from the queue and invokes it (or gives it control),
43+
In more technical terms, the event loop contains a queue of tasks (or "chunks
44+
of work") to be run.
45+
Some tasks are added directly by you, and some indirectly by ``asyncio``.
46+
The event loop pops a task from the queue and invokes it (or "gives it control"),
4847
similar to calling a function, then that task runs.
4948
Once it pauses or completes, it returns control to the event loop.
5049
The event loop will then move on to the next task in its queue and invoke it.
5150
This process repeats indefinitely.
52-
Even if the queue is empty, the event loop continues to cycle (somewhat aimlessly).
51+
Even if the queue is empty, the event loop continues to cycle (somewhat
52+
aimlessly).
5353

5454
Effective execution relies on tasks sharing well: a greedy task could hog
5555
control and leave the other tasks to starve, rendering the overall event loop
@@ -84,7 +84,7 @@ Calling a regular function invokes its logic or body::
8484
>>>
8585

8686
The :ref:`async def <async def>`, as opposed to just a plain ``def``, makes
87-
this an asynchronous function (or coroutine function).
87+
this an asynchronous function (or "coroutine function").
8888
Calling it creates and returns a :ref:`coroutine <coroutine>` object.
8989

9090
::
@@ -101,24 +101,25 @@ Note that calling it does not execute the function::
101101
<coroutine object special_fella at 0x104ed2740>
102102
>>>
103103

104-
The terms "asynchronous function" (or "coroutine function") and "coroutine object"
105-
are often conflated as coroutine.
104+
The terms "asynchronous function" and "coroutine object" are often conflated
105+
as coroutine.
106106
That can be confusing!
107107
In this article, coroutine specifically refers to a coroutine object, or more
108108
precisely, an instance of :data:`types.CoroutineType` (native coroutine).
109109
Note that coroutines can also exist as instances of :class:`collections.abc.Coroutine`
110110
-- a distinction that matters for type checking.
111111

112-
That coroutine represents the function's body or logic.
112+
A coroutine represents the function's body or logic.
113113
A coroutine has to be explicitly started; again, merely creating the coroutine
114114
does not start it.
115115
Notably, the coroutine can be paused and resumed at various points within the
116116
function's body.
117117
That pausing and resuming ability is what allows for asynchronous behavior!
118118

119119
Coroutines and coroutine functions were built by leveraging the functionality
120-
of generators and generator functions.
121-
Recall, a generator function is a function that ``yield``\s, like this one::
120+
of :term:`generators <generator iterator>` and
121+
:term:`generator functions <generator>`.
122+
Recall, a generator function is a function that :keyword:`yield`\s, like this one::
122123

123124
def get_random_number():
124125
# This would be a bad random number generator!
@@ -130,7 +131,7 @@ Recall, a generator function is a function that ``yield``\s, like this one::
130131
yield 4
131132
...
132133

133-
Like, a coroutine function, invoking a generator function does not run it.
134+
Similar to a coroutine function, calling a generator function does not run it.
134135
Instead, it provides a generator object::
135136

136137
>>> get_random_number()
@@ -162,7 +163,7 @@ The recommended way to create tasks is via :func:`asyncio.create_task`.
162163
Creating a task automatically adds it to the event loop's queue of tasks.
163164

164165
Since there's only one event loop (in each thread), ``asyncio`` takes care of
165-
associating the task with the event loop for you. That is, there's no need
166+
associating the task with the event loop for you. As such, there's no need
166167
to specify the event loop.
167168

168169
::
@@ -184,7 +185,7 @@ Unfortunately, it actually does matter which type of object await is applied to.
184185

185186
``await``\ ing a task will cede control from the current task or coroutine to
186187
the event loop.
187-
And while doing so, add a callback to the awaited task's list of callbacks
188+
And while doing so, adds a callback to the awaited task's list of callbacks
188189
indicating it should resume the current task/coroutine when it (the
189190
``await``\ ed one) finishes.
190191
In other words, when that awaited task finishes, it adds the original task
@@ -246,7 +247,7 @@ The event loop then works through its queue, calling ``coro_b()`` and then
246247
A conceptual overview part 2: the nuts and bolts
247248
------------------------------------------------
248249

249-
Part 2 goes into detail on the mechanisms asyncio uses to manage control flow.
250+
Part 2 goes into detail on the mechanisms ``asyncio`` uses to manage control flow.
250251
This is where the magic happens.
251252
You'll come away from this section knowing what await does behind the scenes
252253
and how to make your own asynchronous operators.
@@ -255,27 +256,29 @@ and how to make your own asynchronous operators.
255256
coroutine.send(), await, yield and StopIteration
256257
================================================
257258

258-
asyncio leverages 4 components to pass around control.
259+
``asyncio`` leverages 4 components to pass around control.
259260

260261
:meth:`coroutine.send(arg) <generator.send>` is the method used to start or resume a coroutine.
261262
If the coroutine was paused and is now being resumed, the argument ``arg``
262263
will be sent in as the return value of the ``yield`` statement which originally
263264
paused it.
264-
If the coroutine is being started, as opposed to resumed, ``arg`` must be None.
265+
If the coroutine is being started, as opposed to resumed, ``arg`` must be
266+
``None``.
265267

266268
:ref:`yield <yieldexpr>`, like usual, pauses execution and returns control to the caller.
267-
In the example below, the ``yield`` is on line 3 and the caller is
269+
In the example below, the ``yield``, on line 3, is called by
268270
``... = await rock`` on line 11.
269-
Generally, ``await`` calls the ``__await__`` method of the given object.
270-
``await`` also does one more very special thing: it propagates (or passes along)
271-
any yields it receives up the call-chain.
271+
Generally, ``await`` calls the :meth:`~object.__await__` method of the given
272+
object.
273+
``await`` also does one more very special thing: it propagates (or "passes
274+
along") any ``yield``\ s it receives up the call-chain.
272275
In this case, that's back to ``... = coroutine.send(None)`` on line 16.
273276

274277
The coroutine is resumed via the ``coroutine.send(42)`` call on line 21.
275278
The coroutine picks back up from where it ``yield``\ ed (or paused) on line 3
276279
and executes the remaining statements in its body.
277280
When a coroutine finishes, it raises a :exc:`StopIteration` exception with the
278-
return value attached as an attribute.
281+
return value attached in the :attr:`~StopIteration.value` attribute.
279282

280283
::
281284

@@ -317,7 +320,7 @@ That snippet produces this output:
317320
Coroutine main() finished and provided value: 23.
318321
319322
It's worth pausing for a moment here and making sure you followed the various
320-
ways control flow and values were passed.
323+
ways that control flow and values were passed.
321324

322325
The only way to yield (or effectively cede control) from a coroutine is to
323326
``await`` an object that ``yield``\ s in its ``__await__`` method.
@@ -347,15 +350,17 @@ A future has a few important attributes. One is its state which can be either
347350
"pending", "cancelled" or "done".
348351
Another is its result which is set when the state transitions to done.
349352
Unlike a coroutine, a future does not represent the actual computation to be
350-
done; instead it represents the status and result of that computation, kind of
353+
done; instead, it represents the status and result of that computation, kind of
351354
like a status light (red, yellow or green) or indicator.
352355

353-
:class:`asyncio.Task` subclasses :class:`asyncio.Future` in order to gain these various capabilities.
354-
The prior section said tasks store a list of callbacks, which wasn't entirely true.
356+
:class:`asyncio.Task` subclasses :class:`asyncio.Future` in order to gain
357+
these various capabilities.
358+
The prior section said tasks store a list of callbacks, which wasn't entirely
359+
true.
355360
It's actually the ``Future`` class that implements this logic which ``Task``
356361
inherits.
357362

358-
Futures may be also used directly that is, not via tasks.
363+
Futures may also be used directly (not via tasks).
359364
Tasks mark themselves as done when their coroutine is complete.
360365
Futures are much more versatile and will be marked as done when you say so.
361366
In this way, they're the flexible interface for you to make your own conditions
@@ -401,7 +406,7 @@ preventing other tasks from running.
401406

402407
Below, we use a future to enable custom control over when that task will be marked
403408
as done.
404-
If ``future.set_result()`` (the method responsible for marking that future as
409+
If :meth:`future.set_result() <asyncio.Future.set_result>` (the method responsible for marking that future as
405410
done) is never called, then this task will never finish.
406411
We've also enlisted the help of another task, which we'll see in a moment, that
407412
will monitor how much time has elapsed and accordingly call
@@ -417,16 +422,16 @@ will monitor how much time has elapsed and accordingly call
417422
# Block until the future is marked as done.
418423
await future
419424

420-
We'll use a rather bare object ``YieldToEventLoop()`` to ``yield`` from its
425+
We'll use a rather bare object, ``YieldToEventLoop()``, to ``yield`` from
421426
``__await__`` in order to cede control to the event loop.
422427
This is effectively the same as calling ``asyncio.sleep(0)``, but this approach
423428
offers more clarity, not to mention it's somewhat cheating to use
424429
``asyncio.sleep`` when showcasing how to implement it!
425430

426431
As usual, the event loop cycles through its queue of tasks, giving them control
427432
and receiving control back when they pause or finish.
428-
The ``watcher_task``, which runs the coroutine: ``_sleep_watcher(...)`` will be
429-
invoked once per full cycle of the event loop's queue.
433+
The ``watcher_task``, which runs the coroutine ``_sleep_watcher(...)``, will
434+
be invoked once per full cycle of the event loop's queue.
430435
On each resumption, it'll check the time and if not enough has elapsed, then it'll
431436
pause once again and hand control back to the event loop.
432437
Eventually, enough time will have elapsed, and ``_sleep_watcher(...)`` will
@@ -435,7 +440,7 @@ infinite ``while`` loop.
435440
Given this helper task is only invoked once per cycle of the event loop's queue,
436441
you'd be correct to note that this asynchronous sleep will sleep *at least*
437442
three seconds, rather than exactly three seconds.
438-
Note, this is also of true of: ``asyncio.sleep``.
443+
Note this is also of true of ``asyncio.sleep``.
439444

440445
::
441446

@@ -478,5 +483,6 @@ For reference, you could implement it without futures, like so::
478483
else:
479484
await YieldToEventLoop()
480485

481-
That's all for now. Hopefully you're ready to more confidently dive into some
482-
async programming or check out advanced topics in the :mod:`docs <asyncio>`.
486+
But, that's all for now. Hopefully you're ready to more confidently dive into
487+
some async programming or check out advanced topics in the
488+
:mod:`docs <asyncio>`.

0 commit comments

Comments
 (0)