Search code examples
pythonpython-asyncio

Building a set of tasks with asyncio, but total time takes longer than longest task


In the following code, why does it take 10(0+1+2+3+4) seconds to finish, instead of 4 seconds, when I'm using asyncio?

import asyncio,time

async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(f"what = {what}, at {time.strftime('%X')}")


background_tasks = set()

async def main():
    for i in range(5):
        task = asyncio.create_task(say_after(delay=i,what=i))
        
        # Add task to the set. This creates a strong reference.
        background_tasks.add(task)
        
        await task
        
        # To prevent keeping references to finished tasks forever,
        # make each task remove its own reference from the set after
        # completion:
        task.add_done_callback(background_tasks.discard) # discard is a set method.
        
if __name__=="__main__":
    asyncio.run(main())

The result is in the picture.

enter image description here

Edit: I'm following this official documentation


Solution

  • There is no reason to keep the background_tasks set if you are going to discard it after all tasks are done. Instead, I would use asyncio.gather

    async def main():
        await asyncio.gather(*[
            say_after(delay=i, what=i) for i in range(5)
        ])
    

    In this case, you are not holding additional variables and awaiting all tasks at once. The final code should look like

    import asyncio
    import time
    
    
    async def say_after(delay, what):
        await asyncio.sleep(delay)
        print(f"what = {what}, at {time.strftime('%X')}")
    
    
    async def main():
        await asyncio.gather(*[
            say_after(delay=i, what=i) for i in range(5)
        ])
    
    
    if __name__ == "__main__":
        asyncio.run(main())
    

    Result

    # what = 0, at 09:19:35
    # what = 1, at 09:19:36
    # what = 2, at 09:19:37
    # what = 3, at 09:19:38
    # what = 4, at 09:19:39