Синхронные и асинхронные инструкции

Все инструкции, которые не содержат await (синхронные инструкции), выполняются напрямую в event loop, блокируя выполнение программы. Это означает, что пока такая инструкция выполняется, другие задачи в event loop не могут выполняться. Однако, в этот момент могут выполняться внешние операции.

import asyncio
import time

async def task1():
    print("Task 1 starting")
    time.sleep(2)
    print("Task 1 finished after", time.perf_counter() - start)

async def task2():
    print("Task 2 starting")
    await asyncio.sleep(2)
    print("Task 2 finished after", time.perf_counter() - start)

async def main():
    global start
    start = time.perf_counter()
    await asyncio.gather(task1(), task2())

asyncio.run(main())
Task 1 starting
Task 1 finished after 2.0007838000019547
Task 2 starting
Task 2 finished after 4.014582700001483

В первой задаче мы выполняем блокирующую операцию time.sleep. Вместо неё может быть любая другая блокирующая ресурсоёмкая операция (например, обработка текста ответа сервера).

Эта обработка блокирует event loop, и вторая задача сможет начаться только после завершения первой.

Задача 1 выполняется первой, и выполняет блокирующий код, блокируя цикл на 2 секунды.

Задача 2 выполняется после завершения задачи 1. Поэтому время выполнения здесь 2 + 2 = 4 секунды.

Рассмотрим еще 1 пример:

import asyncio
import time


async def task1():
    print("Task 1 starting")
    await asyncio.sleep(2)
    print("Task 1 finished after", time.perf_counter() - start)


async def task2():
    print("Task 2 starting")
    time.sleep(3)
    print("Task 2 finished after", time.perf_counter() - start)


async def main():
    global start
    start = time.perf_counter()
    await asyncio.gather(task1(), task2())


asyncio.run(main())
Task 1 starting
Task 2 starting
Task 2 finished after 3.00045880000107
Task 1 finished after 3.0006070000017644

В этом примере task1 выполняет асинхронную операцию await asyncio.sleep(2), которая передает управление обратно event loop. Однако, task2 выполняет блокирующую операцию time.sleep(3), блокируя event loop на 3 секунды. Таким образом, хотя asyncio.sleep выполняется конкурентно, event loop не может "переключиться" на завершение asyncio.sleep до тех пор, пока не завершится блокирующий код в task2. В результате общее время выполнения составляет 3 секунды.

Вывод: вызывайте асинхронные методы как можно раньше, чтобы конкурентное выполнение программы началось быстрее.

В каких местах в асинхронном коде желательны асинхронные функции?

Там, где происходит обращение к внешним ресурсам:

  • Файловый ввод-вывод
  • Обращения к различным базам данным
  • Получение и отправка информации по сети Интернет
  • Ожидание внешних программ

Во всех этих случаях старайтесь найти асинхронную библиотеку для своих нужд.

Где искать библиотеки asyncio?

Далее, мы попробуем написать асинхронную программу: скрапер/парсер интернет страниц.