API's for asynchronous events #9131
Replies: 6 comments 7 replies
-
Thanks @peterhinch great question! I will reply in more detail later because this is something that I have lots of thoughts about, but two quick points that aren't GUI-related but kind of broadly related to this discussion. I'd be really interested to hear from people familiar with other languages that also now support async/await such as Javascript/TypeScript, C#, Swift, etc.... I don't think Java has native support, but Kotlin does.
|
Beta Was this translation helpful? Give feedback.
-
I'm 100% in favour of the concept of async events rather than traditional callbacks for the same reasons mentioned earlier, but most importantly the variable scope management and the readable / linear code path. I hate nothing more than trying to debug the callback hell that JS code often becomes. Most of the patterns in use in aioble are an excellent example of how beautifully clean this can be, however in my opinion there's one outlier still; the on-write handlers. These We've (mostly @peitschie) also discovered an issue with these await event task handlers: the order of incoming (often hardware / ble) events can be lost. If two events come in closer together than the time it takes asyncio to service/check all the tasks/events in the loop, then the event that was registered first will get handled first, regardless of which event occurred first. I've been thinking some sort of event queuing might be needed for cases looked this if/when the order of hw events matter (they don't always) but haven't tried to prototype anything yet. |
Beta Was this translation helpful? Give feedback.
-
Adding my 👍 to aioble's async/await based interface. I wrote a fairly complex Bluetooth service (object transfer service) with the lower level call-back (well, IRQ based) ubluetooth interface. When @mattytrentini ported this across to aioble, quite a few lines were saved, but more importantly the logical flow of activities required far less error-prone management of global state. |
Beta Was this translation helpful? Give feedback.
-
I have now applied the event driven approach to some real problems. These are my thoughts. RationaleThe design of
This contrasts with other schedulers (such as Device driversFor easy comptaibility with this approach drivers should expose an asynchronous PrimitivesApplying The fact that they are await WaitAll((event1, event2, WaitAny(event3, event4))).wait() The wa = WaitAny((event1, event2, event3))
await wa.wait()
if wa.event() is event1:
# event1 caused execution to continue The primitives' Application exampleA measuring instrument is started by pressing a button. The measurement normally runs for five seconds. If the sensor does not detect anything, the test runs until it does, however it is abandoned if nothing has been detected after a minute. While running, extra button presses are ignored. During a normal five second run, extra detections from the sensor are ignored. This can readily be coded using callbacks and synchronous or asynchronous code,however the outcome is likely to have a fair amount of ad hoc logic. This is an event based approach: btn = Pushbutton(args) # Has Events for press, release, double, long
bp = btn.press
sn = Sensor(args) # Assumed to have an Event interface.
tm = Ticks_ms(duration=5_000) # Exposes .wait and .clear only.
events = (sn, tm)
async def foo():
while True:
bp.clear() # Ignore prior button press
await bp.wait() # Button pressed
for event in events: # Ignore events that were set prior to this moment
event.clear()
tm.trigger() # Start 5 second timer
try:
await asyncio.wait_for(WaitAll(events).wait(), 60)
except asyncio.TimeoutError:
print("No reading from sensor")
else:
# Normal outcome In my opinion this wins on readability. StatusThere is prototype code so I'm convinced it can be made to work efficiently. I'd welcome any comments, especially in regard to errors of fact. If this approach is common practice I'd be interested to see references. |
Beta Was this translation helpful? Give feedback.
-
This document contains my conclusions on this with event based drivers and primitives. I'd welcome any feedback, even if only "the old bloke's finally lost it" :-) |
Beta Was this translation helpful? Give feedback.
-
I'm a bit late to this party, but just wanted to add a +1 to the principle. I will try to find the time to delve fully into your wrapper classes and document, @peterhinch, but I'm really up against it with a bunch of projects at the moment (sigh). I write all of my MPy code using What I'd really love is for a general approach to this to be come up with that then can be applied to all of the obvious built-in classes, kind of like the button = Pin(2, Pin.IN, pull=Pin.PULL_UP)
while True:
await button.event(Pin.IRQ_FALLING)
print("Button pushed!") And/or an iterable multi-shot, queue version: button = Pin(2, Pin.IN, pull=Pin.PULL_UP)
async for event in button.events(Pin.IRQ_FALLING):
print("Button pushed!") It'd also be great to see I'm an [As an aside on event queues: it might be interesting to come up with a core implementation of event queues that can be merged together to maintain event ordering across different internal IRQs. I suspect that would really require going the whole hog and having an I'm personally more of a fan of methods returning |
Beta Was this translation helpful? Give feedback.
-
The traditional approach in a toolkit such as a GUI is to use callbacks. If a user adjusts a control or clicks on a virtual button, a callback runs which does something like change a variable available to the main application code. This has the merit of being familiar. It also means that the application developer can write conventional synchronous code - although it doesn't preclude using asynchronous code if the application requires it.
In my efforts such as micro-gui and interfaces to mechanical contacts I adopted the callback approach largely out of habit. Callbacks do introduce complications such as how to pass arguments to them and how to retrieve a return value. These aren't insurmountable, but they do add code.
It occurred to me that the fact that
uasyncio
has a very efficientEvent
class offers an alternative to callbacks. A task waiting on anEvent
consumes minimal resources. Further,uasyncio
can support large numbers of waiting tasks. Consider this case where a task waits on a switch closure:The "do something" might set a variable or run some asynchronous code. It could replicate the traditional interface, the callback function being passed to
handle_closure
and running after the event occurred.My suggestion is that the interface to asynchronous objects can be simplified to managing a bound
Event
. Such an interface allows the user to run callbacks if required, but also offers other options. The drawbacks are:Event
s.uasyncio
.I'd be interested to hear any comments. Do any actual GUI's use this kind of approach?
Beta Was this translation helpful? Give feedback.
All reactions