-
-
Notifications
You must be signed in to change notification settings - Fork 32.5k
gh-137026: Add an explainer guide for asyncio #137215
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
base: main
Are you sure you want to change the base?
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
**Unlike tasks, await-ing a coroutine does not cede control!** Wrapping a coroutine in a task first, then ``await``-ing | ||
that would cede control. The behavior of ``await coroutine`` is effectively the same as invoking a regular, | ||
synchronous Python function. Consider this program:: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, I’m not sure why this design decision was made and find it rather confuses the meaning of await: asynchronously wait. If someone here does know, please tell me so I can update this section appropriately! I think providing such context can aid significantly with peoples understanding (myself included!).
For reference, I'd imagine either having coroutines yield in their __await__
or disallowing await coroutine
entirely (thereby effectively mandating people who want to use coroutines must use coroutine.send()
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand your question correctly, it's just that coroutines act as a wrapper over other tasks. Using await
on a coroutine will cede control once that coroutine calls await
on a task.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, agreed! Coroutines will give up control once await
is used on a task or future.
However, I think that coroutines not ceding on await
is somewhat surprising behavior. One of the upsides of asyncio is the precise visibility into where control handoffs happen. This behavior muddles that insight a bit. The person reading the code can't just look for await
's, they need to know the type of the object being await
ed.
I imagine most coroutines do eventually call something which will await
a task or future, but some may not. In a similar vein as that first point, this behavior feels like a sneaky gotcha that could surprise users and lead to implementations that unintentionally hog the event loop.
Finally, I don't really see a strong upside to the current behavior in the face of those downsides. I'd prefer entirely disallowing await coroutine
and instead having authors rely on coroutine.send
. But, of course, I might be missing something!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I imagine most coroutines do eventually call something which will await a task or future, but some may not.
It's not that intuitive, but if no future/task is called down the line, then there's no point in making it async. A function becomes async because it needs to await
something else that is async, and that should go all the way down the line until you hit a case where someone made something async to await
a future/task. If there isn't one, then there was no point in making anything async in the first place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you can still write correct or incorrect programs either way. My concern is that a well designed tool should be hard to misuse. And I don't see an upside for the synchronous await coroutine
behavior, despite this downside.
For a concrete example, consider someone working in a broader async codebase. They want to create functionality to asynchronously wait for a db request from their in-house, custom db. They write something like this. And fail to realize they're unwittingly going to stall the whole event loop, despite the various uses of await
.
async def read_from_db():
while True:
is_db_ready = await check_is_db_ready()
if is_db_ready:
db_record = await fetch_db_record()
return db_record
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's basically syntactic-sugar for yield from (i.e. the syntax that was originally used in 3.4). Therefore it's needed to enter the coroutine (which is basically another name for a generator) and run that code. In other words, anything async
needs to use await
, but it doesn't necessarily yield.
It would be more accurate to say that await
is the only place in the code where we may yield back to the event loop, but there's no guarantee unless you are familiar with the functionality of that code. e.g. In aiohttp, some users may, on the rare occasion, get caught by code processing data slower than it arrives and therefore the code never actually needs to yield to the event loop (because we're never waiting for the socket).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will review further once the lines are wrapped, as now it will invalidate suggestions.
A Conceptual Overview of asyncio | ||
********************************* | ||
|
||
:Author: Alexander Nordin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:Author: Alexander Nordin |
Please see our style guide, new documentation should not have such text, as it discourages others from contributing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment has yet to be addressed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, maybe we should make an exception to this rule here. This isn't getting a whatsnew or blurb entry, so @anordin95 has no other way to be acknowledged for his efforts.
The devguide doesn't do a great job of explaining why author attribution discourages other people from contributing; would you mind pointing me to the issue where this was discussed? I'm curious if this was based on actual contributor feedback or just speculation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was decided by the EB IIRC. It has been done for the latest HOWTO (which I think is the only one since the guide’s introduction).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a link to the EB discussion/decision? I'm not really familiar with how their governance process works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'm ok with the "originally by". I'm actually not opposed to crediting the work. I'm just opposed to have the byline itself. I'm perfectly fine with having something citing a work upon which this is based. As a side note, I'm also not opposed to citing authors inside code comments in general as they are more low-level and help knowing who maintains what and who did what (it's more as a form of contact).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Folks,
Here's some context on.
First, thank you @anordin95 for this PR and the lovely work to make asyncio more understandable and accessible to users. 💐
The @python/editorial-board made a decision to update the style guide to strongly discourage any new documentation from having an author byline. Over the years, many contributors were discouraged from contributing to documents since the original author rejected pull requests to improve a doc. Since documentation is a community resource, the Editorial Board decided that the bylines discouraged contribution more than encouraged them.
In this particular situation, we definitely want to recognize @anordin95 for his good work. I could see several pathways to do so:
- a
seealso
block that links out to the external guide which also mentions that the guide inspired the HOWTO. ❤️ - recognition in What's New under documentation
- recognition in a Discourse thread about the availability of the HOWTO
I hope this clarifies the questions here.
This comment was marked as duplicate.
This comment was marked as duplicate.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, thanks Carol. I'm personally in favor of the seealso
block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow. That was an impressive showing of how to effectively, kindly manage disagreement! Thank you for the context :)
Ah, I see. Bylines can inadvertently embolden some authors to reject otherwise thoughtful, helpful contributions.
I modified to use a seealso
block and removed the byline.
common approaches to concurrency -- multiprocessing, multithreading & asyncio -- and describes where | ||
each is most useful. | ||
|
||
During my own asyncio learning process, a few aspects particually drove my curiosity (read: drove me nuts). You |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Such personalization is discouraged, see this issue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I generally agree that too much personalization or flourish can be distracting, but I think there's a balance to be struck. Sometimes, it can be of great use to the reader. Furthermore, of any place in the docs, I think explainers/HOWTOs are most suited for this kind of writing.
In the issue you referenced, someone also replied to this effect: #62480 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that "you" adds a nice personal touch to tutorials, but I'll leave it up to the documentation experts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was not talking about the „you”, I was instead referring to this:
During my own asyncio learning process, a few aspects particually drove my
curiosity (read: drove me nuts).
Even if for some odd reason we do want to keep this, we should not have an idiom which will be problematic for translators and (as the devguide states, use simple language) readers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume you want to remove the whole sentence, yes? The "odd reason" I think it should be kept is that it adds useful context and relates to the the user; getting confused and even frustrated by asyncio
is okay and happened for the author of the article too. A bit of personality can likely help folks feel less intimidated by confusing subjects. And I feel it's a much more natural lead in to the list of learning objectives that follows.
Similarly I think something is lost when removing a bit of humor -- "(read: drove me nuts)", but I hear you on translation difficulties. I could replace it with something more straightforward like: "(read: confused the heck out of me)".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this has been addressed. For reference, here's the new text:
"You might be curious about some key asyncio
concepts.
You'll be comfortably able to answer these questions by the end of this
article."
- Start sentences on new lines to minimize disruption of diffs.
…Which concurrency do I want" section.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to commit to reviewing this if nobody else has the time. I think is generally an improvement to the documentation, but definitely needs some work still. Thanks for working on this!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are still many minor infringements of the Style guide (e.g. simple English) which could be addressed in one pass.
You can also use Sphinx cross references when discussing specific keywords, functions etc.
A Conceptual Overview of asyncio | ||
********************************* | ||
|
||
:Author: Alexander Nordin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment has yet to be addressed.
A Conceptual Overview of :mod:`!asyncio` | ||
**************************************** | ||
|
||
This article seeks to help you build a sturdy mental model of how :mod:`asyncio` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor, but instead of the word "article", let's use "page".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or “HOWTO,” since that is specifically what it is? That is what the Unicode HOWTO does, for example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"HOWTO" is screaming at me a little too much. I like "page".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm actually a little hesitant. I think "article" emphasizes the style of writing and expected reading approach. Whereas "page" is much closer to other forms of documentation where you typically just jump to whatever you're looking for rather than reading each sentence one after the next.
Building on Stan's idea, I lean towards something like:
"This HOWTO article ..."
Which also gives a reference to the other HOWTO's.
Edit: Could also lowercase the howto, but that might be confused with how-to style articles which are a different part of the Diataxis documentation framework. And the link might make clearer that the reader is not being screamed at, but introduced to a quirky descriptor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see Peters point1, I think linking is overkill, however. This is what Unicode has:
This HOWTO discusses Python’s support for the Unicode specification for representing textual data
Footnotes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually rather like the link. I wasn't aware Python even had this style of documentation (or that other HOWTOs already existed), despite having used the language for over 10 years, until I submitted the Github Issue a week ago. I think the kind of person who likes fundamental explainers is going to be happy to find more!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly LGTM. I'm down to just typos at this point.
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 ``await``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I suggested this before but the commit may have been lost:
clear in a moment when we discuss ``await``. | |
clear in a moment when we discuss :keyword:`await`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I purposely held off linking to await
until the await
section which comes next. With that in mind, what do you think of leaving this as is?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to include the reference. The user doesn't have to click on it, but they can if they want some more general information rather than information suited for an asyncio tutorial.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. Done 👍
@anordin95 @ZeroIntensity I'm going to mark this request changes as a reminder to myself that I want to make sure that we address @kumaraditya303's commments sufficiently prior to any merge (as he and myself as well as a few others are listed as asyncio experts in the devguide). Thanks for working through many of the comments and suggestions. 💪🏼 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Marking as request changes pending @kumaraditya303's re-review.
A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated. Once you have made the requested changes, please leave a comment on this pull request containing the phrase |
Let's just use the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @ZeroIntensity. Good idea.
Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
Co-authored-by: Carol Willing <carolcode@willingconsulting.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've done another pass over the explainer. I've tried to read this from the perspective of someone less familiar with event loops.
I'm still on the fence about using queue, and these suggestions remove reference to the concept of queue.
Overall, this explainer is a nice addition.
@@ -38,6 +39,7 @@ Python Library Reference. | |||
|
|||
General: | |||
|
|||
* :ref:`a-conceptual-overview-of-asyncio` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This item should follow the argparse-tutorial. The section is alphabetized by standard library topic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree re: alphabetization. But these are rendered without the dashes and become "A Conceptual..." and "Annotations..."
You'll be comfortably able to answer these questions by the end of this | ||
article: | ||
|
||
- What's happening behind the scenes when an object is ``await``\ ed? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- What's happening behind the scenes when an object is ``await``\ ed? | |
- What's happening behind the scenes when an object is awaited? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it was this way originally. If I recall correctly @ZeroIntensity suggested the switch. I now actually have a slight preference for await
ed too, but I don't feel very strongly. Y'all should decide.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My original suggestion was to remove the dash from "await-ed". No preference from me on whether await
should be in a code block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, shoot. My bad!
Turns out the only impediment was my bad memory lol. Will move the various occurrences out of code blocks 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m not a fan of this since when translating most languages will have to restructure the sentence to preserve the keyword.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@StanFromIreland Do you prefer keeping the backticks? I don't feel that strongly about it. If maintaining the backticks works better for translations, I'm cool with keeping them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My apologies, my comment is not very clear. I am referring to the existing one with the backticks, since it can not be translated, and it is part of the sentence, it will not make sense in another language. The translator will either have to restructure the sentence to make it somewhat work, or juggle it around (e.g. in parenthesis) as they won't want to remove it. Your suggestion is simpler to translate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So to be clear, we are removing the backticks. Correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Apologies again, I should work on my clarity) Yes, I am in favour of removing them.
In more technical terms, the event loop contains a queue of jobs to be run. | ||
Some jobs are added directly by you, and some indirectly by :mod:`!asyncio`. | ||
The event loop pops a job from the queue and invokes it (or "gives it control"), | ||
similar to calling a function, and then that job runs. | ||
Once it pauses or completes, it returns control to the event loop. | ||
The event loop will then move on to the next job in its queue and invoke it. | ||
This process repeats indefinitely. | ||
Even if the queue is empty, the event loop continues to cycle (somewhat | ||
aimlessly). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In more technical terms, the event loop contains a queue of jobs to be run. | |
Some jobs are added directly by you, and some indirectly by :mod:`!asyncio`. | |
The event loop pops a job from the queue and invokes it (or "gives it control"), | |
similar to calling a function, and then that job runs. | |
Once it pauses or completes, it returns control to the event loop. | |
The event loop will then move on to the next job in its queue and invoke it. | |
This process repeats indefinitely. | |
Even if the queue is empty, the event loop continues to cycle (somewhat | |
aimlessly). | |
Conceptually, the event loop manages jobs to be run. | |
Some jobs are added by you, and some indirectly by :mod:`!asyncio`. | |
The event loop selects a job and invokes it (or "gives it control"), | |
similar to calling a function, and then that job runs. | |
Once the job pauses or completes, it returns control to the event loop. | |
The event loop will then move on to the next job and invoke it. | |
This process repeats indefinitely. | |
Even if no jobs are ready to run, the event loop continues to cycle (somewhat | |
aimlessly). |
# This creates an event loop and indefinitely cycles through | ||
# its queue of tasks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# This creates an event loop and indefinitely cycles through | |
# its queue of tasks. | |
# This creates an event loop and indefinitely cycles through its available work tasks. |
:: | ||
|
||
async def other_work(): | ||
print("I am worker. Work work.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
print("I am worker. Work work.") | |
print("I am a worker. Work work.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The lack of an "a" was actually intentional. Meant to be a bit goofy and reminiscent of some old real-time strategy games. Though, if the grammatical correctness is a concern, how about: "I like work. Work work."?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"I like work." That works for me.
offers more clarity, not to mention it's somewhat cheating to use | ||
``asyncio.sleep`` when showcasing how to implement it! | ||
|
||
As usual, the event loop cycles through its queue of tasks, giving them control |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As usual, the event loop cycles through its queue of tasks, giving them control | |
As usual, the event loop cycles through its tasks, giving them control |
As usual, the event loop cycles through its queue of tasks, giving them control | ||
and receiving control back when they pause or finish. | ||
The ``watcher_task``, which runs the coroutine ``_sleep_watcher(...)``, will | ||
be invoked once per full cycle of the event loop's queue. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
be invoked once per full cycle of the event loop's queue. | |
be invoked once per full cycle of the event loop. |
Eventually, enough time will have elapsed, and ``_sleep_watcher(...)`` will | ||
mark the future as done, and then itself finish too by breaking out of the | ||
infinite ``while`` loop. | ||
Given this helper task is only invoked once per cycle of the event loop's queue, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given this helper task is only invoked once per cycle of the event loop's queue, | |
Given this helper task is only invoked once per cycle of the event loop, |
I am worker. Work work. | ||
I am worker. Work work. | ||
I am worker. Work work. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am worker. Work work. | |
I am worker. Work work. | |
I am worker. Work work. | |
I am a worker. Work work. | |
I am a worker. Work work. | |
I am a worker. Work work. |
Co-authored-by: Carol Willing <carolcode@willingconsulting.com>
done comes from the respect and cooperation of its teammates. | ||
|
||
In more technical terms, the event loop contains a queue of jobs to be run. | ||
Some jobs are added directly by you, and some indirectly by :mod:`!asyncio`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to point out some minor clarifications, feel free to disregard if you don't think it's appropriate in these docs:
It would be more accurate here to say that any code can queue a job, including third-party libraries etc.
The event loop pops a job from the queue and invokes it (or "gives it control"), | ||
similar to calling a function, and then that job runs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strictly speaking, it's not quite a simple queue. Maybe this only matters to low-level asyncio developers though, and general users don't need to understand the difference.
If I remember correctly, essentially, when tasks are being added, they get added to a list of tasks to be run on the next loop cycle. Once all tasks in the current list have been iterated through once (with incomplete tasks getting re-added to the next cycle's list), only then does it move to the next loop cycle, and create a new list for a future cycle.
This is probably roughly the same as the comment below: https://github.com/python/cpython/pull/137215/files#r2250000716
This process repeats indefinitely. | ||
Even if the queue is empty, the event loop continues to cycle (somewhat | ||
aimlessly). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typically we use asyncio.run()
, so the loop continues until that task completes, not indefinitely.
I also don't think the loop cycles aimlessly, the application would basically sleep. e.g. If all your tasks are waiting on I/O (so there are no tasks queued), then it uses the OS's select() call (on the selector event loop) to tell the OS to wake it up when it's received data on those sockets. When it wakes up, it will reschedule the tasks associated with those sockets and run another cycle.
event_loop = asyncio.new_event_loop() | ||
event_loop.run_forever() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest we don't use the low-level APIs here if we're trying to teach users the correct way to utilise asyncio.
A task also maintains a list of callback functions whose importance will become | ||
clear in a moment when we discuss ``await``. | ||
The recommended way to create tasks is via :func:`asyncio.create_task`. | ||
Creating a task automatically adds it to the event loop's queue of tasks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also like how mentioning the queue subtly reinforces the idea that execution is serial (i.e. not parallel).
I would additionally say that it's useful to understand that the order of execution is predictable. Many users are under the impression that "scheduled" means unpredictable order of execution. The tasks should start executing in the order they've been added.
``await`` also does one more very special thing: it propagates (or "passes | ||
along") any ``yield``\ s it receives up the call-chain. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's essentially syntactic sugar for yield from
(which was the actual syntax used in Python 3.4).
and how to make your own asynchronous operators. | ||
|
||
================================================ | ||
coroutine.send(), await, yield and StopIteration |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's useful to understand how it works, but I'd probably rename the header to something like "How coroutines work under-the-hood".
I use similar examples for talks explaining how asyncio works, where I start by showing the more complex things that can be done with generators (that many developers are unaware of) and end up with a very simplistic approximation of an event loop, without using any async/await syntax. (I use the functions interactively in a talk, but the code examples are at: https://gist.github.com/Dreamsorcerer/5c0ddafae6eed073a90cf52fd7cfaede)
By using generators alone with no actual asyncio code though, I think I avoid misleading anybody in thinking that the syntax they see is the appropriate way to write asyncio (which I cover with a couple of points at the end with actual asyncio examples).
The only way to yield (or effectively cede control) from a coroutine is to | ||
``await`` an object that ``yield``\ s in its ``__await__`` method. | ||
That might sound odd to you. You might be thinking: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably worth an example of why this might be a problem and how to deal with it?
For example, if you await
on things which don't yield or are processing some CPU-intensive data, you may need to add await asyncio.sleep(0)
to the code to ensure the code does yield back to the event loop in a reasonable amount of time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additionally on that, it might be worth mentioning the debug features of asyncio, such as the ability to log anything that blocks the loop for more than (by default) 100ms.
**Unlike tasks, await-ing a coroutine does not cede control!** Wrapping a coroutine in a task first, then ``await``-ing | ||
that would cede control. The behavior of ``await coroutine`` is effectively the same as invoking a regular, | ||
synchronous Python function. Consider this program:: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's basically syntactic-sugar for yield from (i.e. the syntax that was originally used in 3.4). Therefore it's needed to enter the coroutine (which is basically another name for a generator) and run that code. In other words, anything async
needs to use await
, but it doesn't necessarily yield.
It would be more accurate to say that await
is the only place in the code where we may yield back to the event loop, but there's no guarantee unless you are familiar with the functionality of that code. e.g. In aiohttp, some users may, on the rare occasion, get caught by code processing data slower than it arrives and therefore the code never actually needs to yield to the event loop (because we're never waiting for the socket).
I agree, using queue as the terminology here isn't completely correct so +1 to remove it. |
:: | ||
|
||
class YieldToEventLoop: | ||
def __await__(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bare yield is used internally by asyncio to implement asyncio.sleep(0) and is special cased, I don't think this is a good example to document as it relies too much on implementation detail.
Explainer guide for asyncio
gh-137026: HOWTO article for asyncio, and a reference to it from the main page of the asyncio docs.
Hi!
I've used Python's
asyncio
a couple times now, but never really felt confident in my mental model of how it fundamentally works and therefore how I can best leverage it. The official docs provide good documentation for each specific function in the package, but, in my opinion, are missing a cohesive overview of the systems design and architecture. Something that could help the user understand the why and how behind the recommended patterns. And a way to help the user make informed decisions about which tool in the asyncio toolkit they ought to grab, or to recognize when asyncio is the entirely wrong toolkit. I thought I'd take a stab at filling that gap and contributing back to a community that's given so much!📚 Documentation preview 📚: https://cpython-previews--137215.org.readthedocs.build/en/137215/howto/a-conceptual-overview-of-asyncio.html