30

I'm building an SMTP server with aiosmtpd and used the examples as a base to build from. Below is the code snippet for the entry point to the program.

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.create_task(amain(loop=loop))
    try:
        loop.run_forever()
    except KeyboardInterrupt:
        pass

When I run the program, I get the following warning:

server.py:61: DeprecationWarning: There is no current event loop
  loop = asyncio.get_event_loop()

What's the correct way to implement this?

pepoluan
  • 6,132
  • 4
  • 46
  • 76
howley
  • 425
  • 1
  • 4
  • 5
  • 1
    Wow! This change in `get_event_loop()` behavior went under the radar for me. I am one of the maintainers of `aiosmtpd`, and we'll try to urgently push a new version to handle this. – pepoluan Dec 28 '22 at 09:05

2 Answers2

42

Your code will run on Python3.10 but as of 3.11 it will be an error to call asyncio.get_event_loop when there is no running loop in the current thread. Since you need loop as an argument to amain, apparently, you must explicitly create and set it.

It is better to launch your main task with asyncio.run than loop.run_forever, unless you have a specific reason for doing it that way. [But see below]

Try this:

if __name__ == '__main__':
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    try:
        asyncio.run(amain(loop=loop))
    except KeyboardInterrupt:
        pass

Added April 15, 2023:

There is a difference between calling asyncio.run(), which I have done here, and calling loop.run_forever() (as in the original question) or loop.run_until_complete(). When I wrote this answer I did not realize that asyncio.run() always creates a new event loop. Therefore in my code above, the variable loop that is passed to amain will not become the "running loop." So my code avoids the DeprecationWarning/RuntimeException, but it doesn't pass a useful loop into amain.

To correct that, replace the line

asyncio.run(amain(loop=loop))

with

loop.run_until_complete(amain(loop=loop))

It would be best to modify amain to obtain the running event loop inside the function instead of passing it in. Then you could launch the program with asyncio.run. But if amain cannot be changed that won't be possible.

Note that run_until_complete, unlike asyncio.run, does not clean up async generators. This is documented in the standard docs.

Paul Cornelius
  • 9,245
  • 1
  • 15
  • 24
  • 1
    do you use `amain` because it would be different from `main` ? – D.L Oct 24 '22 at 18:44
  • 2
    I used `amain` because that was the name used in the original post. Since in the no actual function was provided, the name has no significance. There was no reason to change it. – Paul Cornelius Oct 24 '22 at 22:13
  • 2
    Similar answer @ https://stackoverflow.com/a/68652643/1548275 suggesting `asyncio.new_event_loop()` to replace obsoleted `get_event_loop()`. – Jari Turkia Mar 23 '23 at 08:29
  • 1
    As highlighted by the [comments in a similar question](https://stackoverflow.com/questions/46727787/runtimeerror-there-is-no-current-event-loop-in-thread-in-async-apscheduler/46750562#comment80508235_46750562), this solution may be thread-unsafe. I opted for this solution instead: https://stackoverflow.com/a/69314701/3873799 – alelom Apr 13 '23 at 17:37
  • 2
    the docs suggest this: `asyncio.run(main())` . . . here are the docs: https://docs.python.org/3/library/asyncio-task.html – D.L Apr 14 '23 at 20:13
  • @D.L In the OP's code, his main task requires passing `loop` as an argument to `amain`. That's the reason for explicitly creating a loop here and setting it *before* calling run. Otherwise of course you are right, that's simpler and preferable. The requirements here dictated the two-step approach. – Paul Cornelius Apr 15 '23 at 07:51
  • @alelom There is no way this could be thread-unsafe. All it is doing is starting an event loop, and this couldn't possibly interfere with any activity in another thread. Please explain what you are trying to say. And please justify why you have dropped a similar comment all over the place on this site, criticizing this answer for absolutely no reason. – Paul Cornelius Apr 15 '23 at 08:18
  • There is an important difference between loop.run_forever() and asyncio.run(), which I did not understand when I wrote this answer. The answer has been modified. – Paul Cornelius Apr 15 '23 at 08:44
  • @Paul, where in the docs is mentioned this: "Note that run_until_complete, unlike asyncio.run, does not clean up async generators. This is documented in the standard docs." I'm genuinely interested. How would you run `amain()` in a way that async generators are cleaned up? Thanks! – gmagno Jul 24 '23 at 18:08
  • The docs for asyncio.run https://docs.python.org/3/library/asyncio-runner.html?highlight=asyncio%20run#asyncio.run explictly say that the function finalizes async generators. The docs for asyncio.run_until_complete do not say anything about async generators. The docs for loop.shutdown_async_generators https://docs.python.org/3/library/asyncio-eventloop.html?highlight=run_until_complete#asyncio.loop.shutdown_asyncgens state that "there is no need to call this function if asyncio.run is used." – Paul Cornelius Jul 25 '23 at 01:11
-1
async def ws(f):
    pass


async def callback(*args):
    pass


async def create():
    tasks = [
        asyncio.create_task(
            ws(callback)
        )
    ]

    await asyncio.wait(tasks)


if __name__ == "__main__":
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)

    loop.run_until_complete(create())
seunggabi
  • 1,699
  • 12
  • 12