I have one "parent" sync function and one "child" sync function. I want to run few "child" functions as a result I convert them to coroutine. Child functions/coroutines I run inside loop – for some reason the 1st loop returns result, but the 2nd one throws RuntimeWarning: coroutine 'to_thread' was never awaited
Minimal code:
import asyncio
def some_func(number):
print('task number:', number)
return number
def start():
for r in range(2):
print('r:', r)
tasks = []
for i in range(5):
tasks.append(asyncio.to_thread(some_func, i))
responses = asyncio.gather(*tasks, return_exceptions=True)
loop = asyncio.get_event_loop()
results = loop.run_until_complete(responses)
loop.close()
print('results:', results)
if __name__ == '__main__':
start()
Result:
r: 0
task number: 0
task number: 1
task number: 2
task number: 3
task number: 4
results: [0, 1, 2, 3, 4]
r: 1
Traceback (most recent call last):
File "/media/antonio/www/tourbase/booking-system/back-end/arctic_reservations/src/test.py", line 27, in <module>
start()
File "/media/antonio/www/tourbase/booking-system/back-end/arctic_reservations/src/test.py", line 18, in start
responses = asyncio.gather(*tasks, return_exceptions=True)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/asyncio/tasks.py", line 827, in gather
fut = _ensure_future(arg, loop=loop)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/asyncio/tasks.py", line 680, in _ensure_future
return loop.create_task(coro_or_future)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/asyncio/base_events.py", line 434, in create_task
self._check_closed()
File "/usr/lib/python3.11/asyncio/base_events.py", line 519, in _check_closed
raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
sys:1: RuntimeWarning: coroutine 'to_thread' was never awaited
If I put loop = asyncio.get_event_loop() and loop.close() outside of for loop, everything is ok.
Could anybody explain why it happens? As I understand, the 2nd loop should create a new event loop.
Python v3.11.6
>Solution :
At the end of the first iteration, you closed the event loop.
As I understand, the 2nd loop should create a new event loop.
No, get_event_loop returns the existing event loop. When you try to submit new tasks to it in the second iteration, it tells you that you cannot do this because it is already closed.
The function that always creates a new event loop is called new_event_loop.
But instead of trying to manage multiple event loops yourself by asyncio.get_event_loop and loop.run_until_completed and loop.close, you can simply use a single event loop which asyncio.run handles for you automatically (start needs to become a coroutine):
import asyncio
def some_func(number):
print('task number:', number)
return number
async def start():
for r in range(2):
print('r:', r)
tasks = []
for i in range(5):
tasks.append(asyncio.to_thread(some_func, i))
results = await asyncio.gather(*tasks, return_exceptions=True)
print('results:', results)
if __name__ == '__main__':
asyncio.run(start())