Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confused by python async for loop--executes sequentially

I am new to asyncio and trying to understand basic for loop behavior. The code below executes sequentially, but my naive assumption was that while the sleeps are occurring, other items could be fetched via the for loop and start processing. But that doesn't seem to happen.

For example, while the code is "doing something else with 1" it seems like it could fetch the next item from the loop and start working on it while waiting for the sleep to end on item 1. But when I run, it executes sequentially with pauses for the sleeps like a non-async program.

What am I missing here?

import asyncio


class CustomIterator():
    def __init__(self):
        self.counter = 0

    def __aiter__(self):
        return self

    async def __anext__(self):
        if self.counter >= 3:
            raise StopAsyncIteration
        await asyncio.sleep(1)
        self.counter += 1
        return self.counter


async def f(item):
    print(f"doing something with {item}")
    await asyncio.sleep(3)


async def f2(item):
    print(f"doing something else with {item}")
    await asyncio.sleep(2)


async def do_async_stuff():
    async for item in CustomIterator():
        print(f"got {item}")
        await f(item)
        await f2(item)


if __name__ == '__main__':
    asyncio.run(do_async_stuff())

Output:

got 1
doing something with 1
doing something else with 1
got 2
doing something with 2
doing something else with 2
got 3
doing something with 3
doing something else with 3
like image 694
chacmool Avatar asked Oct 12 '25 10:10

chacmool


2 Answers

The entire point of await is to create a sequence and yield point: when you await foo, the goal is to give control to the executor so it can run other tasks, until whatever foo is resolves and you get control back.

If you want to create concurrency in async code, you need to either:

  1. create tasks (using the appropriately named create_task), each task is an other thing the executor can run while one task is await-ing
  2. compose coroutines together using utilities like wait, as_completed, or gather which register multiple objects against the executor at the same time, this way their asynchronous resolution will overlap rather than chain
like image 187
Masklinn Avatar answered Oct 14 '25 23:10

Masklinn


I think you have a common misunderstanding of how async works. You have written your program to be synchronous. await foo() says to call foo(), and feel free to go do something else while we're waiting for foo to return with its answer. Likewise, getting the next element from your custom iterator says "get the next element of this iterator, but feel free to go do something else while waiting for the result". In both cases, you have nothing else to do, so your code wants.

If it is safe for two things in your code to run at once, it is your job to say so, using appropriate primitives.

like image 44
Frank Yellin Avatar answered Oct 14 '25 23:10

Frank Yellin