-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Make keypad select/poll'able, which leads to async goodness #6712
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
Conversation
While I haven't benchmarked this, I believe it's more efficient than an async def aget(event_queue, interval):
while True:
if (event := event_queue.get()) is not None:
return event
await asyncio.sleep(interval) because the checking for event availability happens without even entering Python bytecode, within |
This allows a small wrapper class to be written ```py class AsyncEventQueue: def __init__(self, events): self._events = events async def __await__(self): yield asyncio.core._io_queue.queue_read(self._events) return self._events.get() def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): pass ``` and used to just "await" the next event: ```py async def key_task(): print("waiting for keypresses") with keypad.KeyMatrix([board.D4], [board.D5]) as keys, AsyncEventQueue(keys.events) as ev: while True: print(await ev) ``` Because checking the empty status of the EventQueue does not enter CircuitPython bytecode, it's assumed (but not measured) that this is more efficient than an equivalent loop with an `await async.sleep(0)` yield and introduces less latency than any non-zero sleep value.
7f71115
to
76f03a2
Compare
Refined to use the existing
|
I wrote a test program which runs on raspberry pi pico. It creates a separate Keys object and asyncio task for each GPIO, as well as a "logic task" which just counts how long it takes to I tested with three ways of polling, and with 1 and 29 key tasks. With 29 key tasks (all GPIOs used):
I also tested with just one Keys task:
And with zero Keys tasks:
While this is a contrived example, it shows the large benefit to program responsiveness, especially as the number of tasks grows. Busy-polling 29 waiting tasks in a CircuitPython loop makes the compute task wait ~13.5ms each time it sleeps. Doing it with the core code is only ~0.5ms (and under 100 microseconds more than with 0 tasks to busy-poll). Even though it is still "just" a polling loop, it makes a huge difference that it can do a polling cycle without entering the Python VM. |
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 for this inspired change!
Why do you think a 0 sleep is worse? That's confusing to me. |
I think that when all the tasks are doing an |
I updated the initial comment code so that it works with current versions of asyncio. This code depends on internal details of the asyncio library and as such can be broken by internal changes to asyncio. (adafruit/Adafruit_CircuitPython_asyncio@fb068e2) |
This allows a small wrapper class to be written
and used to just "await" the next event: