11

I'm getting started to AsyncIO and AioHTTP, and i'm writing some basic code to get familiar with the syntax. I tried the following code that should perform 3 requests concurrently:

import time
import logging
import asyncio
import aiohttp
import json
from aiohttp import ClientSession, ClientResponseError
from aiocfscrape import CloudflareScraper

async def nested(url):
    async with CloudflareScraper() as session:
        async with session.get(url) as resp:
            return await resp.text()

async def main():
    URL = "https://www.binance.com/api/v3/exchangeInfo"
    await asyncio.gather(nested(URL), nested(URL), nested(URL))

asyncio.run(main())

Here is the output:

raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed

I don't understand why do i get that error, can anyone help me on this?

10
  • What operating system are you running the code on? Commented Sep 12, 2020 at 13:22
  • I'm running this on Windows 10, Python 3.8
    – JayK23
    Commented Sep 12, 2020 at 13:27
  • 1
    It looks like you're running into this issue. But it's very likely that your code is actually working, despite the unsightly message - you're just returning the contents from main() without printing it, so you have no output other than the message. Commented Sep 12, 2020 at 13:29
  • Thank you a lot! So it's actually aiohttp raising the error. I'm relieved because i had no idea what could be behind this error
    – JayK23
    Commented Sep 12, 2020 at 13:39
  • 1
    This is a know issue with Window, have a look at github.com/encode/httpx/issues/914. You can fix this issue by setting the event loop policy
    – Greg
    Commented Sep 13, 2020 at 20:28

2 Answers 2

14

Update

Originally I was recommending Greg's answer below:

import asyncio
import sys

if sys.platform:
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

Turned out, using WindowsSelectorEventLoop has functionality issues such as:

  • Can't support more than 512 sockets
  • Can't use pipe
  • Can't use subprocesses

due to the fact that Windows uses I/O completion Ports unlike *nix - Therefore SelectorEventLoop is not designed for Windows nor is implemented as full.

If those limitations matters to you - You might be better off using lengthy workaround in this answer.

Check out more about differences at documents.

Or alternatively, consider using Trio over asyncio, which is much more stable and consistent.

import trio


async def task():
    await trio.sleep(5)


trio.run(task) 

Original post

I've finally figured out how to keep ProactorEventLoop running, preventing unsuccessful IO closure.

Really not sure why windows' Event loop is so faulty, as this also happens for asyncio.open_connection and asyncio.start_server.

To workaround this, you need to run event loop in forever loop and close manually.

Following code will cover both windows and other environments.

import asyncio
from aiocfscrape import CloudflareScraper


async def nested(url):
    async with CloudflareScraper() as session:
        async with session.get(url) as resp:
            return await resp.text()


async def main():
    await nested("https://www.binance.com/api/v3/exchangeInfo")


try:
    assert isinstance(loop := asyncio.new_event_loop(), asyncio.ProactorEventLoop)
    # No ProactorEventLoop is in asyncio on other OS, will raise AttributeError in that case.

except (AssertionError, AttributeError):
    asyncio.run(main())
    
else:
    async def proactor_wrap(loop_: asyncio.ProactorEventLoop, fut: asyncio.coroutines):
        await fut
        loop_.stop()

    loop.create_task(proactor_wrap(loop, main()))
    loop.run_forever()

This code will check if new EventLoop is ProactorEventLoop.
If so, keep loop forever until proactor_wrap awaits main and schedules loop stop.

Else - possibly all other OS than Windows - doesn't need these additional steps, simply call asyncio.run() instead.

IDE like Pycharm will complain about passing AbstractEventLoop to ProactorEventLoop parameter, safe to ignore.

7
  • 1
    I tried this and i didn't get the error indeed. I hope this answer gets enough attention, since there are a lot of questions on the same problem, from what i've seen. Thank you!
    – JayK23
    Commented Sep 12, 2020 at 15:24
  • 1
    Glad it helped. How ironic that I couldn't figured this out with my repo for months - time to implement this workaround to see if this solution also applies to socket server.
    – jupiterbjy
    Commented Sep 12, 2020 at 23:48
  • It's an interesting solution and I do like it. However instead of manually closing the event loop, why not just set the policy?
    – Greg
    Commented Sep 13, 2020 at 20:26
  • Didn't even know there was option for it - After reading document it says "since python 3.8 ProactorEventLoop is default for windows." Not sure why this faulty loop using IOCP is default, but I'm also concerned about going against dev's decision.
    – jupiterbjy
    Commented Sep 14, 2020 at 10:30
  • Surprised to see it works fluently, thanks for tip!
    – jupiterbjy
    Commented Sep 14, 2020 at 10:36
8

Whilst this has been answered and accepted. You can fix this issue with one line of code: asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

Event loop is closed is a known issue on Windows (see https://github.com/encode/httpx/issues/914). I suspect this will be fixed in later versions of Python. To get around the error, simply set the event loop policy to WindowsSelectorEventLoopPolicy.

If you plan to run the code on non-windows environment; then you'll want to either add an if statement to prevent error. E.g: if sys.platform == 'win32'. Or add code to set the policies.

Working example:

import asyncio
from aiocfscrape import CloudflareScraper
import sys

async def nested(url):
    async with CloudflareScraper() as session:
        async with session.get(url) as resp:
            print(resp.status)
            return await resp.text()

async def main():
    URL = "https://www.binance.com/api/v3/exchangeInfo"
    await asyncio.gather(nested(URL), nested(URL), nested(URL))

# Only preform check if your code will run on non-windows environments.
if sys.platform == 'win32':
    # Set the policy to prevent "Event loop is closed" error on Windows - https://github.com/encode/httpx/issues/914
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

asyncio.run(main())
2
  • 1
    BEWARE! Even though this answer might hide away the exception, it degrades functionality: stackoverflow.com/questions/67964463
    – T.Todua
    Commented Aug 18, 2022 at 9:07
  • @T.Todua Nice catch, didn't saw this one - I'll add it at top of my answer so people hopefully see the limitations. Not being able to use subprocess on WindowsSelectorEventLoop is kinda big deal for some,
    – jupiterbjy
    Commented Sep 30, 2022 at 0:15

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.