python / asyncio

asyncio historical repository
https://docs.python.org/3/library/asyncio.html
1.04k stars 177 forks source link

Reawaiting an instance of a coroutine for a periodic Task #413

Closed mbrunnen closed 8 years ago

mbrunnen commented 8 years ago

My idea was to create a periodic task, inherited from the asyncio.Task, which calls given coroutine periodically. A minimal working example follows here:

#!/usr/bin/env python
import asyncio

class CyclicTask(asyncio.Task):
    def __init__(self, wrapped_coro, loop):
        super(CyclicTask, self).__init__(wrapped_coro, loop=loop)

    @staticmethod
    async def run(loop, coro):
        while True:
            loop.set_task_factory(None)
            await loop.create_task(coro)

    @staticmethod
    def create(loop, func):
        return CyclicTask(CyclicTask.run(loop, func), loop)

async def main(loop):
    async def my_func():
        await asyncio.sleep(1)
        print('Hello!')

    loop.set_task_factory(CyclicTask.create)
    cyclic = loop.create_task(my_func())

    await cyclic

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main(loop))

But then I get this error, because I'm reawaiting the same instance of a coroutine as described here: RuntimeError: cannot reuse already awaited coroutine Probably there is an elegant way to avoid this problem, I am pretty new to this...

Martiusweb commented 8 years ago

Hi,

I'm not sure you can do what you want this way.

When you invoke a coroutine function, it returns an object which is the coroutine, which can be seen as a "reference" to a function execution context which is paused when "await" is reached. By design, a coroutine is not meant to be rewound and replayed, rather, you want to get a new instance of the coroutine function.

You use a task to control how the coroutine instance is executed on the loop, a bit like a process executes an instance of a program, with its own attributes.

Also, changing the task_factory of the loop will have side effects you don't expect. Until CyclicTask.run() is executed by the loop (and this will not happen in create() but later, asynchronously), any created task will be a CyclicTask. This is likely to break the internals of asyncio, as it may schedule tasks which are not meant to be executed again.

You probably don't need to extend Task, but maybe a coroutine function helper will be closer to what you want to do:

async def repeat_forever(coroutine, *args, **kwargs):
   while True:
       await coroutine(*args, **kwargs)

task = loop.create_task(repeat_forever(my_func)))
loop.run_until_complete(task)

or even:

loop.run_until_complete(repeat_forever(my_func))

If you want more help, this bug tracker may not be the best place, the mailing list (https://groups.google.com/forum/?fromgroups#!forum/python-tulip) seems more appropriate.

asvetlov commented 8 years ago

I think the issue can be closed.

Martiusweb commented 8 years ago

Let's close it then.