pythonpython-3.xpython-asyncio

Python asyncio.gather returning Future attached to differnt loop exception


I am using Python 3.13.2.

I am going through the Python asyncio tutorial here. At around 10:20 timestamp, the instructor shows an example of asyncio.gather with a coroutine accepting a Future.

Here is a sample code that is not working for me using asyncio.gather. How to gather the results of multiple get_result calls?

In [162]: import asyncio

In [163]: from typing import Awaitable

In [164]: async def get_result(awaitable: Awaitable) -> str:
     ...:      try:
     ...:          result = await awaitable
     ...:      except Exception as e:
     ...:          print("oops!", e)
     ...:          return "no result"
     ...:      else:
     ...:          return result
     ...: 

In [165]: loop = asyncio.new_event_loop()

In [166]: f = asyncio.Future(loop=loop)

In [167]: loop.call_later(20, f.set_result, "final result")
     ...: 
     ...: loop.run_until_complete(asyncio.gather(get_result(f), get_result(f), get_result(f)))
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
Cell In[167], line 3
      1 loop.call_later(20, f.set_result, "final result")
----> 3 loop.run_until_complete(asyncio.gather(get_result(f), get_result(f), get_result(f)))

File /Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/tasks.py:884, in gather(return_exceptions, *coros_or_futures)
    882 for arg in coros_or_futures:
    883     if arg not in arg_to_fut:
--> 884         fut = ensure_future(arg, loop=loop)
    885         if loop is None:
    886             loop = futures._get_loop(fut)

File /Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/tasks.py:746, in ensure_future(coro_or_future, loop)
    742         raise TypeError('An asyncio.Future, a coroutine or an awaitable '
    743                         'is required')
    745 if loop is None:
--> 746     loop = events.get_event_loop()
    747 try:
    748     return loop.create_task(coro_or_future)

File /Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/events.py:716, in BaseDefaultEventLoopPolicy.get_event_loop(self)
    713     self.set_event_loop(self.new_event_loop())
    715 if self._local._loop is None:
--> 716     raise RuntimeError('There is no current event loop in thread %r.'
    717                        % threading.current_thread().name)
    719 return self._local._loop

RuntimeError: There is no current event loop in thread 'MainThread'.

I also tried using await asyncio.gather but got another error like below,

In [168]: loop = asyncio.new_event_loop()

In [169]: f = asyncio.Future(loop=loop)

In [170]: loop.call_later(10, f.set_result, "final result")
     ...: 
     ...: await asyncio.gather(get_result(f))
oops! Task <Task pending name='Task-28075' coro=<get_result() running at <ipython-input-164-a1176dbd480d>:3> cb=[gather.<locals>._done_callback() at /Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/tasks.py:820]> got Future <Future pending> attached to a different loop
Out[170]: ['no result']

It looks like asyncio.gather() is creating a task attached to a new loop instead of my created loop. Is it possible to use gather using my created loop?

The instructor's video is 5 years old and I assume asyncio have changed until now.


Solution

  • The docs for f = asyncio.Future(loop=loop) states:

    Deprecated since version 3.10: Deprecation warning is emitted if loop is not specified and there is no running event loop.

    I don't find this warning to be very clear but I believe it means that loop should be an already running loop. But if we ensure it is a running loop, then you cannot use loop.run_until_complete().

    import asyncio
    from typing import Awaitable
    
    async def get_result(awaitable: Awaitable) -> str:
        try:
            result = await awaitable
        except Exception as e:
            print("oops!", e)
            return "no result"
        else:
            return result
    
    async def main():
        loop = asyncio.get_running_loop()
        f = asyncio.Future(loop=loop)
        loop.call_later(20, f.set_result, "final result")
        print(await asyncio.gather(get_result(f), get_result(f), get_result(f)))
    
    asyncio.run(main())
    

    Prints:

    ['final result', 'final result', 'final result']