Learning uasyncio and futures
Posted: Thu Nov 15, 2018 9:23 pm
Hi, I've been playing with micropython and have a situation where I want to collect results concurrently from a number of slow (ie. a few seconds) sensors. I thought this could be a job for uasyncio and futures, but I have seen that uasyncio focuses on coroutines in preference to futures for performance reasons. I've dabbled with Lua coroutines but never found them intuitive to work with, and I find futures/promises much easier to work with conceptually.
I'm aware of the additional asyn.py library of concurrency primitives, and the implementation of asynio.gather() using "barriers", which I don't fully understand but looks quite elegant and is probably the "right" way to do it.
But it seemed like quite a lot of machinery to do something I was hoping was going to be quite simple, so I thought I'd have a go at doing some "poor man's" futures using "awaitable" classes and polling. I think it is kind of working - it is not the most elegant, and is probably broken in a number of regards, but it has the advantage that I understand what is going on!
I'm posting the code here for critique - please feel free to rip it to shreds! Is this a bad approach? I've made no attempt to deal with exceptions or timeouts. Is there a simple way to check for completion without polling? (I think the barrier way is probably right, I'm just curious about other approaches). Should I just use asyn.py?
[code]
import uasyncio as asyncio
class PoorFuture(object):
def __init__(self, coro):
self.coro = coro
self.started = False
self.completed = False
self.result = None
def __await__(self):
self.started = True
self.result = await self.coro
self.completed = True
return self.result
__iter__ = __await__
async def as_task(self):
'''Convenience method to turn the future into a task'''
return await self
async def slow_task(seconds, answer):
'''Demo function to build coros that simulate slow-running tasks'''
for i in range(seconds):
print(i, 'of', seconds)
await asyncio.sleep(1)
return answer
async def poor_gather(futures):
'''Schedule a list of futures, wait for them to complete, and return the list of results'''
loop = asyncio.get_event_loop()
# start all the futures as tasks on the event loop
for f in futures:
loop.create_task(f.as_task())
# poll until all the futures have completed
finished = False
while not(finished):
await asyncio.sleep_ms(100)
print([f.completed for f in futures])
# check if they have all finished
finished = all(f.completed for f in futures)
results = [f.result for f in futures]
print('results:', results)
return results
futures = [
PoorFuture(slow_task(3, 'banana')),
PoorFuture(slow_task(5, 'satsuma')),
PoorFuture(slow_task(1, 'mango')),
]
loop = asyncio.get_event_loop()
loop.run_until_complete(poor_gather(futures))
[/code]
I'm aware of the additional asyn.py library of concurrency primitives, and the implementation of asynio.gather() using "barriers", which I don't fully understand but looks quite elegant and is probably the "right" way to do it.
But it seemed like quite a lot of machinery to do something I was hoping was going to be quite simple, so I thought I'd have a go at doing some "poor man's" futures using "awaitable" classes and polling. I think it is kind of working - it is not the most elegant, and is probably broken in a number of regards, but it has the advantage that I understand what is going on!
I'm posting the code here for critique - please feel free to rip it to shreds! Is this a bad approach? I've made no attempt to deal with exceptions or timeouts. Is there a simple way to check for completion without polling? (I think the barrier way is probably right, I'm just curious about other approaches). Should I just use asyn.py?
[code]
import uasyncio as asyncio
class PoorFuture(object):
def __init__(self, coro):
self.coro = coro
self.started = False
self.completed = False
self.result = None
def __await__(self):
self.started = True
self.result = await self.coro
self.completed = True
return self.result
__iter__ = __await__
async def as_task(self):
'''Convenience method to turn the future into a task'''
return await self
async def slow_task(seconds, answer):
'''Demo function to build coros that simulate slow-running tasks'''
for i in range(seconds):
print(i, 'of', seconds)
await asyncio.sleep(1)
return answer
async def poor_gather(futures):
'''Schedule a list of futures, wait for them to complete, and return the list of results'''
loop = asyncio.get_event_loop()
# start all the futures as tasks on the event loop
for f in futures:
loop.create_task(f.as_task())
# poll until all the futures have completed
finished = False
while not(finished):
await asyncio.sleep_ms(100)
print([f.completed for f in futures])
# check if they have all finished
finished = all(f.completed for f in futures)
results = [f.result for f in futures]
print('results:', results)
return results
futures = [
PoorFuture(slow_task(3, 'banana')),
PoorFuture(slow_task(5, 'satsuma')),
PoorFuture(slow_task(1, 'mango')),
]
loop = asyncio.get_event_loop()
loop.run_until_complete(poor_gather(futures))
[/code]