Search code examples
pythonpython-asyncioaiohttp

Maddening phenomenon about aiohttp and asyncio


Below I will use two examples to illustrate this phenomenon:

example 1:

import asyncio
import time
import sys
import aiohttp
import threading

async def request():
    print("enter function")
    timeout = aiohttp.ClientTimeout(total = 5)
    async with aiohttp.ClientSession(timeout= timeout) as session:
        async with session.get("A url that is unreachable on the network") as rsp:
            if rsp.status != 200:
                print("error")
            print("ok")
asyncio.run(request())

example 2:

import asyncio
import time
import sys
import aiohttp
import threading

def _thread_running_loop(loop):
    asyncio.set_event_loop(loop)
    loop.run_forever()
    print("loop exit.")

async def request():
    print("enter function")
    timeout = aiohttp.ClientTimeout(total = 5)
    async with aiohttp.ClientSession(timeout= timeout) as session:
        async with session.get("A url that is unreachable on the network") as rsp:
            if rsp.status != 200:
                print("error")
            print("ok")

loop = asyncio.new_event_loop()
thread = threading.Thread(target=_thread_running_loop,args=(loop,))
thread.setDaemon(True)
thread.start()
asyncio.run_coroutine_threadsafe(request(), loop)
time.sleep(1000000)

At 'example 1', it return 'raise asyncio.TimeoutError' which i expected. But at 'example 2', it's like falling asleep, no response or exception, only printing "enter function".I can't figure out where the problem is, can anyone help.

"update example 2"

import asyncio
import time
import sys
import aiohttp
import threading
import logging

def _thread_running_loop(loop):
    asyncio.set_event_loop(loop)
    loop.run_forever()
    print("loop exit.")

async def request():
    print("enter function")
    timeout = aiohttp.ClientTimeout(total = 5)
    async with aiohttp.ClientSession(timeout= timeout) as session:
        async with session.get("A url that is unreachable on the network") as rsp:
            if rsp.status != 200:
                print("error")
            print("ok")

async def call_request():
    try:
        return await request()
    except Exception as e:
        logging.exception(e)

def main():
    loop = asyncio.new_event_loop()
    thread = threading.Thread(target=_thread_running_loop,args=(loop,))
    thread.setDaemon(True)
    thread.start()
    while True:
        print("call_request")
        asyncio.run_coroutine_threadsafe(call_request(), loop)
        asyncio.run_coroutine_threadsafe(call_request(), loop)
        print("wait 100s")
        time.sleep(100)
main()
time.sleep(1000000)

Solution

  • TL;DR you never collect the result of the coroutine, so the exception raised in the asyncio thread can't propagate. Add a call to .result() and the snippet will work as expected.

    But at 'example 2', it's like falling asleep, no response or exception, only printing "enter function". I can't figure out where the problem is, can anyone help.

    It's falling asleep because you've literally told it to fall asleep:

    time.sleep(1000000)
    

    run_coroutine_threadsafe(a, b) means "submit coroutine object a to run in event loop b". By default it doesn't wait for the coroutine to finish because its whole point is to allow your thread to continue doing other things. However, it returns a concurrent.futures.Future (not to be confused with asyncio.Future) that you can use to check whether the computation is complete, wait for it to complete, and retrieve its result or exception. If you never do any of that, you will never notice the exception because it happens in a completely different thread. But if you do, e.g. by changing the invocation to:

    asyncio.run_coroutine_threadsafe(request(), loop).result()
    

    ...you achieve two things: you wait for the coroutine to complete, and the exception raised (if any) propagates to your main thread, making it behave like example 1.

    Note that example 1 doesn't really print anything either, it just raises an InvalidURL exception, as does example 2 with the above fix.