Что произойдет, если в асинхронной корутине использовать time.sleep()?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
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!
Заключение
Критичные правила асинхронности:
-
НИКОГДА не используй
time.sleep()в async функциях- Используй
await asyncio.sleep() - Это блокирует весь event loop
- Парализует все остальные корутины
- Используй
-
Все операции должны быть асинхронными
- HTTP:
aiohttp,httpx - БД:
asyncpg,asyncmy,motor - Файлы:
aiofiles - Сокеты:
asyncio.open_connection()
- HTTP:
-
Для CPU-bound операций используй
run_in_executor()- Не замораживает event loop
- Использует thread pool
-
Помни: async это не параллелизм, а конкурентность
- Один поток выполнения
- Event loop переключается между корутинами
- Когда одна ждёт (I/O), другая может выполняться
Вывод: Использование time.sleep() в асинхронной корутине — это одна из самых частых ошибок, которая может замедлить приложение в 10-100 раз.