pythonpython-asyncioevent-loop

Unexpected behaviour in Python event loop


I have the following piece of Async code in Python.

import asyncio

async def get_some_values_from_io():
    print("Getsome value Executing...")
    await asyncio.sleep(3)
    return [100,200]

vals = []

async def fetcher():
    while True:
        print("Fetcher Executing...")
        io_vals = await get_some_values_from_io()

        for val in io_vals:
            vals.append(io_vals)

async def monitor():
    while True:
        print("Monitor Executing...")
        print (len(vals))

        await asyncio.sleep(3)

async def main():
    t1 = asyncio.create_task(fetcher())
    t2 = asyncio.create_task(monitor())
    await asyncio.gather(t1, t2)

asyncio.run(main())

print("Rest of the method is executing....")

Both the async functions being called into are calling async.sleep() method with considerable time to sleep. While both of them sleep, the last print stetment print("Rest of the method is executing....") must be run.

But what is is getting printed is: (it just keeps going in fact)

Fetcher Executing...
Getsome value Executing...
Monitor Executing...
0
Fetcher Executing...
Getsome value Executing...
Monitor Executing...
2
...

My understanding is that the whole Python programs is just single thread (GIL) and the event loop too would share the GIL. Is it not correct?

Also, there are mentions of run_in_executor() method which says that CPU bound tasks can be run using this method outside of event loop. So this means, there is another thread running parallelly besides event loop? This contradicts the fact of GIL.


Solution

  • [This is my reply to the second question below]

    This is normal Python, it's not magic :) You are calling "fetcher()" (inside the "main" coroutine when you create the task) so that fetcher coroutine will start executing -- it's doesn't matter if it's ever awaited, since it cannot know anything about future events -- but since it's a coroutine, it will be paused when it hits the first await. Similar for the other coroutine.

    Since those tasks (t1 and t2) are never awaited, the main coroutine will almost immediately exit. But the prints happened before any awaits -- so those have executed. If you also add print statements after the various await statements, and then also call gather you will see the difference.

    Btw - It seems a little strange to me to create these tasks from inside another coroutine. I think it would be simpler to write

    async def main():
        await fetcher()
        await monitor()