12

Inside my async handler I want to wait until task's state is changed. For now, I just check the state in an endless loop and wait. Here is an example, the wait_until_done function:

import asyncio


class LongTask:
    state = 'PENDING'

my_task = LongTask()


def done():
    my_task.state = 'DONE'

async def wait_until_done():
    while True:
        if my_task.state == 'PENDING':
            await asyncio.sleep(2)
        else:
            break
    print("Finally, the task is done")


def main(loop, *args, **kwargs):
    asyncio.ensure_future(wait_until_done())
    loop.call_later(delay=5, callback=done)

loop = asyncio.get_event_loop()
main(loop)
loop.run_forever()

Is there a better way for doing that?

Nathan Arthur
  • 8,287
  • 7
  • 55
  • 80
Sergey Belash
  • 1,433
  • 3
  • 16
  • 21
  • 1
    Observer pattern is probably what you want to use. Make the object "observable" you then register a handler as an observer to your object so when the state change it will call what ever method you want. https://stackoverflow.com/questions/1904351/python-observer-pattern-examples-tips – Rob Jul 03 '17 at 18:48

1 Answers1

17

Just to avoid confusion: I guess you are not talking about asyncio.Task, but some variable state instead, right?

In this case you have Future and synchronization primitives that allows you to wait some thing changed asynchronously.

If you need to switch between two states, asyncio.Event is probably what you want. Here's little examle:

import asyncio


my_task = asyncio.Event()


def done():
    my_task.set()



async def wait_until_done():
    await my_task.wait()  # await until event would be .set()
    print("Finally, the task is done")


async def main():
    loop.call_later(delay=5, callback=done)
    await wait_until_done()


loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(main())
finally:
    loop.run_until_complete(loop.shutdown_asyncgens())
    loop.close()

Upd:

More complex example that keeps LongTask interface:

import asyncio



class LongTask:
    _event = asyncio.Event()

    @property
    def state(self):
        return 'PENDING' if not type(self)._event.is_set() else 'DONE'

    @state.setter
    def state(self, val):
        if val == 'PENDING':
            type(self)._event.clear()
        elif val == 'DONE':
            type(self)._event.set()
        else:
            raise ValueError('Bad state value.')

    async def is_done(self):
        return (await type(self)._event.wait())

my_task = LongTask()


def done():
    my_task.state = 'DONE'



async def wait_until_done():
    await my_task.is_done()
    print("Finally, the task is done")


async def main():
    loop.call_later(delay=5, callback=done)
    await wait_until_done()


loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(main())
finally:
    loop.run_until_complete(loop.shutdown_asyncgens())
    loop.close()
Mikhail Gerasimov
  • 36,989
  • 16
  • 116
  • 159
  • Yes, the task is a regular object, not asyncio.Task. I thought about the Event(), but your solution is not suitable: long story short, I can not touch the `done` func, it should just change state of the task. – Sergey Belash Jul 03 '17 at 19:08
  • @SergeyBelash, I added another example that keeps `done` func unchanged. – Mikhail Gerasimov Jul 03 '17 at 20:09