@@ -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
1514curiosity (read: drove me nuts).
1615You should be able to comfortably answer all these questions by the end
1716of 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--------------------------------------------
2826A 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==========
3533Event Loop
3634==========
3735
38- Everything in asyncio happens relative to the event loop.
36+ Everything in `` asyncio `` happens relative to the event loop.
3937It's the star of the show.
4038It's kind of like an orchestra conductor or military general.
4139It's behind the scenes managing resources.
4240Some power is explicitly granted to it, but a lot of its ability to get things
4341done 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"),
4847similar to calling a function, then that task runs.
4948Once it pauses or completes, it returns control to the event loop.
5049The event loop will then move on to the next task in its queue and invoke it.
5150This 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
5454Effective execution relies on tasks sharing well: a greedy task could hog
5555control 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
8686The :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" ).
8888Calling 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.
106106That can be confusing!
107107In this article, coroutine specifically refers to a coroutine object, or more
108108precisely, an instance of :data: `types.CoroutineType ` (native coroutine).
109109Note 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.
113113A coroutine has to be explicitly started; again, merely creating the coroutine
114114does not start it.
115115Notably, the coroutine can be paused and resumed at various points within the
116116function's body.
117117That pausing and resuming ability is what allows for asynchronous behavior!
118118
119119Coroutines 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.
134135Instead, 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`.
162163Creating a task automatically adds it to the event loop's queue of tasks.
163164
164165Since 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
166167to 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
186187the 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
188189indicating it should resume the current task/coroutine when it (the
189190``await ``\ ed one) finishes.
190191In 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
246247A 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.
250251This is where the magic happens.
251252You'll come away from this section knowing what await does behind the scenes
252253and how to make your own asynchronous operators.
@@ -255,27 +256,29 @@ and how to make your own asynchronous operators.
255256coroutine.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.
261262If the coroutine was paused and is now being resumed, the argument ``arg ``
262263will be sent in as the return value of the ``yield `` statement which originally
263264paused 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.
272275In this case, that's back to ``... = coroutine.send(None) `` on line 16.
273276
274277The coroutine is resumed via the ``coroutine.send(42) `` call on line 21.
275278The coroutine picks back up from where it ``yield ``\ ed (or paused) on line 3
276279and executes the remaining statements in its body.
277280When 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
322325The 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".
348351Another is its result which is set when the state transitions to done.
349352Unlike 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
351354like 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.
355360It's actually the ``Future `` class that implements this logic which ``Task ``
356361inherits.
357362
358- Futures may be also used directly that is, not via tasks.
363+ Futures may also be used directly ( not via tasks) .
359364Tasks mark themselves as done when their coroutine is complete.
360365Futures are much more versatile and will be marked as done when you say so.
361366In this way, they're the flexible interface for you to make your own conditions
@@ -401,7 +406,7 @@ preventing other tasks from running.
401406
402407Below, we use a future to enable custom control over when that task will be marked
403408as 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
405410done) is never called, then this task will never finish.
406411We've also enlisted the help of another task, which we'll see in a moment, that
407412will 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.
422427This is effectively the same as calling ``asyncio.sleep(0) ``, but this approach
423428offers more clarity, not to mention it's somewhat cheating to use
424429``asyncio.sleep `` when showcasing how to implement it!
425430
426431As usual, the event loop cycles through its queue of tasks, giving them control
427432and 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.
430435On each resumption, it'll check the time and if not enough has elapsed, then it'll
431436pause once again and hand control back to the event loop.
432437Eventually, enough time will have elapsed, and ``_sleep_watcher(...) `` will
@@ -435,7 +440,7 @@ infinite ``while`` loop.
435440Given this helper task is only invoked once per cycle of the event loop's queue,
436441you'd be correct to note that this asynchronous sleep will sleep *at least *
437442three 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