pythonpython-asynciopython-multithreadingaiortc

Python-asyncio - using timers/threading to terminate async


I have an async coroutine that I want to terminate using a timer/thread. The coroutine is based of this example from aiortc.

args = parse_args()
client = Client(connection, media, args.role)

# run event loop
loop = asyncio.get_event_loop()

try:
    timer = None
    if args.timeout:
        print("Timer started")
        timer = threading.Timer(args.timeout, loop.run_until_complete, args=(client.close(),))
        timer.start()

    loop.run_until_complete(client.run())

    if timer:
        timer.join()
except KeyboardInterrupt:
    pass
finally:
    # cleanup
    loop.run_until_complete(client.close())

This does not work and raises RuntimeError('This event loop is already running')

Why does this raise the error? My guess is that it's because the loop is running on a different thread. However, creating a new loop doesn't work as it gets a future attached to a different loop.

def timer_callback():
    new_loop = asyncio.new_event_loop()
    new_loop.run_until_complete(client.close())

Following that, how can I use a timer to end the script?


Solution

  • Following that, how can I use a timer to end the script?

    You can call asyncio.run_coroutine_threadsafe() to submit a coroutine to an event loop running in another thread:

        if args.timeout:
            print("Timer started")
            timer = threading.Timer(
                args.timeout,
                asyncio.run_coroutine_threadsafe,
                args=(client.close(), loop),
            )
            timer.start()
    

    Note, however, that since you're working with asyncio, you don't need a dedicated thread for the timer, you could just create a coroutine and tell it to wait before doing something:

        if args.timeout:
            print("Timer started")
            async def close_after_timeout(): 
                await asyncio.sleep(args.timeout)
                await client.close()
            loop.create_task(close_after_timeout())