-
-
Notifications
You must be signed in to change notification settings - Fork 8.3k
extmod/asyncio: Make SingletonGenerator create_task()-aware. #16656
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: master
Are you sure you want to change the base?
extmod/asyncio: Make SingletonGenerator create_task()-aware. #16656
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #16656 +/- ##
=======================================
Coverage 98.58% 98.58%
=======================================
Files 167 167
Lines 21590 21590
=======================================
Hits 21285 21285
Misses 305 305 ☔ View full report in Codecov by Sentry. |
Code size report:
|
941fdce
to
3ca0ed3
Compare
This commit allows the awaitable returned by asyncio.sleep/sleep_ms() to be passed directly to asyncio.create_task(). Although the documentation says that sleep/sleep_ms() are coroutines and create_task() accepts coroutines, this is not the case; sleep/sleep_ms() actually return a SingletonGenerator(), which is missing a send() method. > === import asyncio > === asyncio.run(asyncio.sleep_ms(2000)) > === > Traceback (most recent call last): > File "<stdin>", line 3, in <module> > File "asyncio/core.py", line 1, in run > File "asyncio/core.py", line 1, in create_task > TypeError: coroutine expected This is easily solved by implementing the missing send() method such as: > === import asyncio > === from time import ticks_ms as ticks, ticks_add > === > === class SingletonGenerator2(asyncio.SingletonGenerator): > === def send(self, x): > === self.__next__() > === > === def sleep_ms(t, sgen=SingletonGenerator2()): > === sgen.state = ticks_add(ticks(), max(0, t)) > === return sgen > === > === asyncio.run(sleep_ms(2000)) > === > >>> Signed-off-by: Takayuki 'January June' Suwa <jjsuwa_sys3175@yahoo.co.jp>
3ca0ed3
to
c1ba02c
Compare
Thanks for the contribution. Do you have any examples of code that would use this pattern, to create a task that simply waits for some time? |
Before I get to the code examples, we should first clear up some of the issues that As I mentioned before, the documentation says:
and:
In the past, I tried to feed a coroutine that sleeps for a few seconds directly to
For now, it would be fine to wrap it like this, but rather than burdening users with the trouble, it would be better to make |
I am currently working on implementing a simple PLC-like device control with no strict constraints on response latency using mPy asyncio on the ESP32. As anyone with even a little experience with device control knows, it is essential to have a time limit when waiting for some kind of signal response, and it is often necessary to display the operation to the operator - for example, by blinking a lamp. So,
As a beginner to mPy asyncio, I struggled for a while to figure out how to clearly create an awaitable that performs the above operation...
It's frustrating for me to have to use |
asyncio.sleep calls asyncio.sleep_ms. This is a normal function, not async. Internally, it uses the asyncio.SingletonGenerator to accomplish the task. It was used to prevent memory allocation. You could patch it, if you want. patch_aio_sleep.py import asyncio
from asyncio import sleep as _sleep_generator
async def asleep(delay: float | int):
await _sleep_generator(delay)
asyncio.sleep = asleep
del asleep main.py import patch_aio_sleep
import asyncio
import time
async def worker():
while True:
await asyncio.sleep(1)
print(time.time())
print("Direct call of asyncio.sleep(1)")
asyncio.run(asyncio.sleep(1))
print("Usage of await asyncio.sleep in an async function.")
asyncio.run(worker()) I guess that this code would allocate memory somewhere in the asyncio module. I think this is also the cause, why it wasn't changed yet. |
At the moment, I am already trying to avoid the issue by wrapping things up in such a way, and of course this pull request is based on that. To further explain the reason for this pull request, this situation is not compatible with CPython.
If you type the above into the CPy REPL it will wait 2 seconds with no issues. |
Summary
This commit allows the awaitable returned by
asyncio.sleep/sleep_ms()
to be passed directly toasyncio.create_task()
.Although the documentation says that
sleep/sleep_ms()
are coroutines andcreate_task()
accepts coroutines, this is not the case;sleep/sleep_ms()
actually return aSingletonGenerator()
, which is missing asend()
method.This is easily solved by implementing the missing
send()
method.Testing
The principle is as follows:
Trade-offs and Alternatives
In exchange for adding just two lines of source code and one method definition, we no longer have to manually
async def
wrap awaitables that wait for a specified time; the benefits are obvious.