peterhinch / micropython-async

Application of uasyncio to hardware interfaces. Tutorial and code.
MIT License
744 stars 169 forks source link

Feature request - WaitAny accepts task #116

Closed sandyscott closed 9 months ago

sandyscott commented 10 months ago

Hi,

I've found a few situations in my current project where I'd quite like to use a task as one of the inputs to WaitAny.

e.g:

async def background_coro():
    # Insert work here...
    await asyncio.sleep(5)

async def main()
    switch = ESwitch(#some pin)
    background_task = asyncio.create_task(background_coro())

    await WaitAny((switch.close, background_task).wait()
    background_task.cancel()

I can easily solve this with passing around Event objects, but it muddies the picture compared to how simple waiting for the task alone would be (ie. await background_coro())

I appreciate there may be lots of good reasons why this can't work, I just thought I'd share a little frustration in case there's something I've missed.

peterhinch commented 9 months ago

[EDIT] I think this ELO class will do what you want. It takes an arbitrary coroutine or a running task and converts it to an ELO.

class ELO:
    def __init__(self, coro, *args, **kwargs):
        self.coro = coro
        self.args = args
        self.kwargs = kwargs

    async def wait(self):
        if isinstance(self.coro, asyncio.Task):
            return await self.coro
        # Coroutine
        return await self.coro(*self.args, **self.kwargs)

async def background_coro(t):  # Demo of arg passing
    # Insert work here...
    await asyncio.sleep(t)

async def main()
    switch = ESwitch(#some pin)

    await WaitAny((switch.close, ELO(background_task, 5))
    background_task.cancel()

I will incorporate into events.py but I need to ensure that WaitAll handles return values (the wait methods of existing ELO instances don't return a value). I'll report back when this is done and documented.

Thank you for pointing me to a useful enhancement.

sandyscott commented 9 months ago

Thank you! That's a really tidy solution.

peterhinch commented 9 months ago

I have now pushed an update. The ELO class has some changes but remains very compact.

The changes were necessary because, unlike an Event, a Task can return a value. It can also be cancelled. There are no changes to WaitAll or WaitAny, and cancellation is handled in such a way that WaitAll and WaitAny behave as if the Task had terminated normally. The application can determine how the Task terminated.

It is documented and there is a test script.

I'll close the issue as complete, but comments are of course welcome.