asyncio call_soon: mixing blocking and non blocking calls

  Kiến thức lập trình

I’m going through asyncio in Python, writing random snippets to teach myself how things work. I stumbled on a behavior that I do not understand and am hoping someone can shed some light on this. Let’s say I have the following snippet

def blocking():
    logger.info("blocking called")
    time.sleep(3)
    logger.info("blocking done")
    pass

async def nonblocking():
    logger.info("nonblocking called")
    await asyncio.sleep(1)
    logger.info("nonblocking done")
    pass

async def amain():
    loop = asyncio.get_running_loop()
    loop.call_soon(blocking)
    await nonblocking()
    pass


if __name__ == "__main__":
    s = timeit.default_timer()
    asyncio.run(amain())
    logger.info(f"elapsed {timeit.default_timer() - s}")

Then, which is surprising to me, this code takes in total 3 seconds to execute – as long as the blocking sleep. I’m not sure why this is not taking 4 seconds instead. To add to my confusion, if I change the await nonblocking() for an await asyncio.gather(nonblocking(), nonblocking(), nonblocking()) then suddenly the code does take 4 seconds. Obviously, the asyncio.gather call is expected to take 1 second, but I do not understand why I go from 3 to 4 seconds if I replace my single coro await by the gather await. Why does the above snippet take 3 seconds and not 4? What is call_soon actually doing in this case?

If I add another loop.call_soon(blocking) immediately after the current one, then first blocking and nonblocking are called, and after 3 seconds elapse and both are done, blocking is called for the second time. So I don’t fully understand how call_soon works.

LEAVE A COMMENT