← Назад к вопросам

Что произойдет, если в асинхронной корутине использовать time.sleep()?

2.2 Middle🔥 161 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

time.sleep() в асинхронной корутине: блокирование event loop

Это типичный вопрос, который разделяет опытных разработчиков от новичков. За 10+ лет работы с async/await в Python я видел множество ошибок, связанных с неправильным использованием блокирующих операций. Давайте разберём что происходит и почему это критично.

Что произойдет

import asyncio
import time

async def fetch_data():
    print("Start fetching")
    time.sleep(5)  # ❌ БЛОКИРУЕТ весь event loop на 5 секунд
    print("Done fetching")
    return "data"

async def main():
    # Запускаем две корутины
    task1 = fetch_data()
    task2 = fetch_data()
    
    # Ожидаем обе
    results = await asyncio.gather(task1, task2)
    print(results)

# Ожидаемое время: 5 сек (параллельно)
# Реальное время: 10 сек (последовательно!)

Проблема: time.sleep() — это блокирующая операция. Она замораживает весь event loop, и другие корутины не могут выполняться.

Почему это проблема: концепция event loop

Event loop — это сердце асинхронной программы:

Event Loop работает так:

1. Берёт корутину 1 (fetch_data)
2. Выполняет до первого await
3. Если есть ожидание (I/O) → переходит на корутину 2
4. Выполняет корутину 2 до первого await
5. Если корутина 1 готова → возвращается на неё
6. Повторяет циклически

РЕЗУЛЬТАТ: "параллельное" выполнение (на одном потоке!)

С time.sleep() это ломается:

Время    Event Loop
0s        Начало корутины 1
0s        time.sleep(5) ← БЛОКИРУЕТ!
0s-5s     Event loop ЗАМОРОЖЕН, ничего не может делать
5s        time.sleep завершился
5s        Конец корутины 1
5s        Начало корутины 2
5s        time.sleep(5) ← БЛОКИРУЕТ СНОВА!
5s-10s    Event loop ЗАМОРОЖЕН
10s       Конец корутины 2

Итого: 10 секунд вместо 5

Демонстрация проблемы

import asyncio
import time

# ❌ ПЛОХО: блокирующий sleep
async def slow_fetch_bad():
    print(f"[{time.time():.1f}] Start")
    time.sleep(2)  # Блокирует весь event loop!
    print(f"[{time.time():.1f}] End")
    return "result"

async def test_blocking():
    print(f"[{time.time():.1f}] Starting tasks")
    
    # Запускаем 3 задачи
    tasks = [slow_fetch_bad() for _ in range(3)]
    results = await asyncio.gather(*tasks)
    
    print(f"[{time.time():.1f}] All done")

# Вывод:
# [0.0] Starting tasks
# [0.0] Start      <- задача 1
# [2.0] End        <- 2 сек спустя
# [2.0] Start      <- задача 2 (не параллельно!)
# [4.0] End        <- ещё 2 сек
# [4.0] Start      <- задача 3
# [6.0] End        <- ещё 2 сек
# [6.0] All done   <- ИТОГО 6 СЕКУНД вместо 2

# ✅ ХОРОШО: асинхронный sleep
async def fast_fetch_good():
    print(f"[{time.time():.1f}] Start")
    await asyncio.sleep(2)  # Не блокирует!
    print(f"[{time.time():.1f}] End")
    return "result"

async def test_async():
    print(f"[{time.time():.1f}] Starting tasks")
    
    tasks = [fast_fetch_good() for _ in range(3)]
    results = await asyncio.gather(*tasks)
    
    print(f"[{time.time():.1f}] All done")

# Вывод:
# [0.0] Starting tasks
# [0.0] Start      <- задача 1
# [0.0] Start      <- задача 2 (ПАРАЛЛЕЛЬНО!)
# [0.0] Start      <- задача 3 (ПАРАЛЛЕЛЬНО!)
# [2.0] End        <- все 3 одновременно
# [2.0] End
# [2.0] End
# [2.0] All done   <- ИТОГО 2 СЕКУНДЫ

Правильное решение: asyncio.sleep()

import asyncio

# ✅ Правильный способ
async def fetch_with_timeout():
    print("Fetching...")
    await asyncio.sleep(2)  # Асинхронный sleep, не блокирует
    print("Done!")
    return "data"

# Использование
async def main():
    # Запускаем 100 параллельных операций
    tasks = [fetch_with_timeout() for _ in range(100)]
    results = await asyncio.gather(*tasks)
    print(f"Fetched {len(results)} items")

asyncio.run(main())

# Время: ~2 сек (все параллельно)
# Вместо: ~200 сек (если бы использовали time.sleep())

Блокирующие операции и как их обрабатывать

import asyncio
import time
import requests
from concurrent.futures import ThreadPoolExecutor

# ❌ ПЛОХО: блокирующий HTTP запрос
async def fetch_bad():
    response = requests.get('https://api.example.com/data')  # Блокирует!
    return response.json()

# ✅ ХОРОШО: асинхронный HTTP запрос
import aiohttp

async def fetch_good():
    async with aiohttp.ClientSession() as session:
        async with session.get('https://api.example.com/data') as response:
            return await response.json()  # Не блокирует

# ❌ ПЛОХО: блокирующий файловый ввод
async def read_file_bad():
    with open('large_file.txt', 'r') as f:
        data = f.read()  # Может блокировать!
    return data

# ✅ ХОРОШО: асинхронный файловый ввод
import aiofiles

async def read_file_good():
    async with aiofiles.open('large_file.txt', mode='r') as f:
        data = await f.read()  # Не блокирует
    return data

# ❌ ПЛОХО: блокирующие CPU операции
async def compute_bad():
    # Вычисление факториала (блокирует на долгое время)
    result = 1
    for i in range(1, 1000000):
        result *= i  # CPU-bound, блокирует!
    return result

# ✅ ХОРОШО: передать в thread pool
def compute_cpu_intensive():
    result = 1
    for i in range(1, 1000000):
        result *= i
    return result

async def compute_good():
    loop = asyncio.get_event_loop()
    with ThreadPoolExecutor() as pool:
        result = await loop.run_in_executor(pool, compute_cpu_intensive)
    return result

Таблица: Блокирующие vs Асинхронные операции

╔════════════════════════╦═════════════════════╦═══════════════════════╗
║ Операция               ║ Блокирующая         ║ Асинхронная           ║
╠════════════════════════╬═════════════════════╬═══════════════════════╣
║ Задержка               ║ time.sleep(2)       ║ await asyncio.sleep(2)║
║ HTTP запросы           ║ requests.get()      ║ aiohttp/httpx         ║
║ Чтение файлов          ║ open().read()       ║ aiofiles              ║
║ БД запросы             ║ psycopg2            ║ asyncpg/asyncmy       ║
║ Подпроцессы            ║ subprocess.run()    ║ asyncio.create_subprocess ║
║ Socket операции        ║ socket.recv()       ║ asyncio streams        ║
║ CPU-bound              ║ Python код          ║ loop.run_in_executor() ║
╚════════════════════════╩═════════════════════╩═══════════════════════╝

Реальный пример: веб-скрейпер

import asyncio
import aiohttp
import time

# ❌ МЕДЛЕННЫЙ способ: последовательно
async def scrape_slow(urls):
    import requests
    
    results = []
    for url in urls:
        response = requests.get(url)  # Ждём 1 сек за запрос
        results.append(response.text)
    # Итого: 100 сек для 100 URL'ов
    return results

# ✅ БЫСТРЫЙ способ: параллельно
async def scrape_fast(urls):
    async with aiohttp.ClientSession() as session:
        tasks = []
        for url in urls:
            task = session.get(url)
            tasks.append(task)
        
        responses = await asyncio.gather(*tasks)
        results = [await resp.text() for resp in responses]
    # Итого: 1 сек для 100 URL'ов (10x быстрее!)
    return results

# Сравнение времени
async def compare():
    urls = [f'https://api.example.com/item/{i}' for i in range(100)]
    
    # Медленный способ
    start = time.time()
    # await scrape_slow(urls)  # 100 сек
    print(f"Slow: {time.time() - start}s")
    
    # Быстрый способ
    start = time.time()
    # await scrape_fast(urls)  # 1 сек
    print(f"Fast: {time.time() - start}s")

Типичные ошибки

# ❌ ОШИБКА 1: time.sleep в цикле
async def bad_loop():
    for i in range(5):
        time.sleep(1)  # Блокирует!
        print(i)

# ✅ ПРАВИЛЬНО
async def good_loop():
    for i in range(5):
        await asyncio.sleep(1)  # Не блокирует
        print(i)

# ❌ ОШИБКА 2: блокирующий вызов в async функции
async def bad_nested():
    result = requests.get('https://...')  # Блокирует!
    return result

# ✅ ПРАВИЛЬНО
async def good_nested():
    async with aiohttp.ClientSession() as session:
        async with session.get('https://...') as resp:
            result = await resp.json()  # Не блокирует
    return result

# ❌ ОШИБКА 3: забыли await
async def bad_missing_await():
    task = asyncio.sleep(5)  # Не ждём!
    print("Instantly done")

# ✅ ПРАВИЛЬНО
async def good_with_await():
    await asyncio.sleep(5)  # Ждём
    print("Done after 5 seconds")

Инструмент для отладки: asyncio debug mode

import asyncio

async def my_function():
    time.sleep(1)  # Блокирующая операция

# Включаем debug mode
asyncio.run(my_function(), debug=True)

# Вывод:
# RuntimeWarning: coroutine 'my_function' was never awaited
# WARNING: coroutine was destroyed but it's pending!

Заключение

Критичные правила асинхронности:

  1. НИКОГДА не используй time.sleep() в async функциях

    • Используй await asyncio.sleep()
    • Это блокирует весь event loop
    • Парализует все остальные корутины
  2. Все операции должны быть асинхронными

    • HTTP: aiohttp, httpx
    • БД: asyncpg, asyncmy, motor
    • Файлы: aiofiles
    • Сокеты: asyncio.open_connection()
  3. Для CPU-bound операций используй run_in_executor()

    • Не замораживает event loop
    • Использует thread pool
  4. Помни: async это не параллелизм, а конкурентность

    • Один поток выполнения
    • Event loop переключается между корутинами
    • Когда одна ждёт (I/O), другая может выполняться

Вывод: Использование time.sleep() в асинхронной корутине — это одна из самых частых ошибок, которая может замедлить приложение в 10-100 раз.