Как использовать `async для` в Python? - PullRequest
1 голос
/ 16 мая 2019

Я имею в виду, что я получу от использования async for. Вот код, который я пишу с async for, AIter(10) можно заменить на get_range().

Но код работает как синхронизация, а не асинхронность.

import asyncio

async def get_range():
    for i in range(10):
        print(f"start {i}")
        await asyncio.sleep(1)
        print(f"end {i}")
        yield i

class AIter:
    def __init__(self, N):
        self.i = 0
        self.N = N

    def __aiter__(self):
        return self

    async def __anext__(self):
        i = self.i
        print(f"start {i}")
        await asyncio.sleep(1)
        print(f"end {i}")
        if i >= self.N:
            raise StopAsyncIteration
        self.i += 1
        return i

async def main():
    async for p in AIter(10):
        print(f"finally {p}")

if __name__ == "__main__":
    asyncio.run(main())

Результат, который я исключил, должен быть:

start 1
start 2
start 3
...
end 1
end 2
...
finally 1
finally 2
...

Однако реальный результат:

start 0
end 0
finally 0
start 1
end 1
finally 1
start 2
end 2

Я знаю, что могу получить ожидаемый результат, используя asyncio.gather или asyncio.wait.

Но мне трудно понять, что я получил, используя async for здесь вместо простого for.

Как правильно использовать async for, если я хочу перебрать несколько объектов Feature и использовать их, как только один из них будет завершен. Например:

async for f in feature_objects:
    data = await f
    with open("file", "w") as fi:
        fi.write()

1 Ответ

4 голосов
/ 16 мая 2019

Но мне трудно понять, что я получил, используя async for здесь вместо простого for.

Основное недоразумение заключается в том, что вы, похоже, ожидаете, что async for автоматически распараллелит итерацию. Он этого не делает, он просто позволяет последовательную итерацию по асинхронному источнику . Например, вы можете использовать async for для перебора строк, поступающих из потока TCP, сообщений из веб-сокета или записей базы данных из драйвера асинхронной БД.

Вы не можете сделать ничего из перечисленного выше с обычным for, по крайней мере, без блокировки цикла событий, потому что for вызывает __next__ как функцию блокировки и не ожидает ее результат. Вы не можете компенсировать это, ожидая каждого элемента вручную, потому что for ожидает, что __next__ будет сигнализировать об окончании итерации, вызывая исключение - и если __next__ является сопрограммой, исключение не будет отображаться до его ожидания. Вот почему async for был введен не только в Python, но и в других языках с async / await и обобщенными for.

Если вы хотите запустить итерации параллельно, вам нужно запустить их как параллельные сопрограммы и использовать asyncio.as_completed или эквивалент для получения их результатов по мере их поступления:

async def x(i):
    print(f"start {i}")
    await asyncio.sleep(1)
    print(f"end {i}")
    return i

for f in asyncio.as_completed([x(i) for i in range(10)]):
    result = await f
    # ... do something with the result ...
...