1

Consider this program, where the mainloop and the coroutine to stop it are actually implemented by a library I'm using.

import asyncio
import signal

running = True

async def stop():
    global running
    print("setting false")
    running = False
    await asyncio.sleep(3)
    print("reached end")

async def mainloop():
    while running:
        print("loop")
        await asyncio.sleep(1)

def handle_signal():
    loop.create_task(stop())

loop = asyncio.get_event_loop()
loop.add_signal_handler(signal.SIGINT, handle_signal)
loop.run_until_complete(mainloop())
loop.close()

I need to call the stop coroutine to stop the mainloop when the program recieves a signal. Although when scheduling the stop coroutine using asyncio.BaseEventLoop.create_task it first stops the mainloop which stops the event loop and the stop coroutine can't finish:

$ ./test.py 
loop
loop
loop
^Csetting false
Task was destroyed but it is pending!
task: <Task pending coro=<stop() done, defined at ./test.py:7> wait_for=<Future pending cb=[Task._wakeup()]>>

How to add the coroutine to the running event loop while making the event loop wait until it is complete?

1 Answer 1

2

As you discovered, the problem is that the event loop is only waiting for mainloop() to complete, leaving stop() pending, which asyncio correctly complains about.

If handle_signal and the top-level code are under your control, you can easily replace looping until mainloop completes with looping until a custom coroutine completes. This coroutine would invoke mainloop and then wait for the cleanup code to finish:

# ... omitted definition of mainloop() and stop()

# list of tasks that must be waited for before we can actually exit
_cleanup = []

async def run():
    await mainloop()
    # wait for all _cleanup tasks to finish
    await asyncio.wait(_cleanup)

def handle_signal():
    # schedule stop() to run, and also add it to the list of
    # tasks run() must wait for before it is done
    _cleanup.append(loop.create_task(stop()))

loop = asyncio.get_event_loop()
loop.add_signal_handler(signal.SIGINT, handle_signal)
loop.run_until_complete(run())
loop.close()

Another option, which doesn't require the new run() coroutine (but still requires the modified handle_signal), is to issue a second run_until_complete() after mainloop completes:

# handle_signal and _cleanup defined as above

loop = asyncio.get_event_loop()
loop.add_signal_handler(signal.SIGINT, handle_signal)
loop.run_until_complete(mainloop())
if _cleanup:
    loop.run_until_complete(asyncio.wait(_cleanup))
loop.close()
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.