3

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.

2 Answers 2

2

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']
Sign up to request clarification or add additional context in comments.

1 Comment

I see that get_running_loop() will only work inside an async function definition. In IPython shell, when called directly it will throw RuntimeError: no running event loop error as expected.
1

For one thing, the web is full of this .run_until_complete call to run asyncio code in Python - it really complicates things - just create a coroutine function as an entry point- everything that would be called in the run_until_complete you either schedule or await in the body of that coroutine, and then do asyncio.run(entrypoint()) (or usually main() rather than entrypoint) - it really can make life easier.

This will simply run as intend, no lifecycle problems:


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())

I didn't watch the tutorial you linked, but there is a chance the author has entirely missed the point of async at all - scheduling futures like this, for one, is a somewhat low level feature, I may have wrote two or three times in hundreds of code (or at least in hundreds of other answers here). The more natural code just do "await" and then for simpler code await asyncio.gather(...) to run things concurrently. (From Python 3.11, asyncio TaskGroups and TaskGroup.create_task may yield some more "correct" code than gather - but it didn't "catch" as a preferred pattern)

My advice if for you to try and see some tutorial from another author rather than trying to understand what is going on here. It really looks like the way he does it is so much "not the intended way" to work with asyncio that it simply broke across Python versions, as asyncio got more mature.

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.