Асинхронное программирование
Эта страница находится в процессе подготовки и не является финальной версией. Содержание будет дорабатываться и обновляться в течение текущего семестра.
Корутины и async/await
Корутина — функция, помеченная async def. Она может приостанавливать своё выполнение с помощью await:
import asyncio
async def fetch_data(url: str) -> str:
print(f"Начинаем загрузку {url}")
await asyncio.sleep(1) # имитация I/O
print(f"Загрузка {url} завершена")
return f"data from {url}"
async def main():
result = await fetch_data("https://example.com")
print(result)
asyncio.run(main())
Корутина — это обобщение генератора. Генераторы используют yield для ленивых вычислений, корутины используют await для приостановки до завершения асинхронной операции.
Event Loop — цикл событий
Event loop — центральный компонент asyncio. Он управляет выполн ением корутин, обработкой I/O-событий и планированием задач.
Порядок работы:
- Инициализация событийного цикла
- Извлечение задачи из очереди
- Выполнение задачи или приостановка до готовности I/O
- Обработка завершённых операций
- Переход к следующей задаче
- Повтор до завершения всех задач
Event loop работает в одном потоке. Блокирующие вызовы (например, time.sleep(), requests.get()) замораживают весь цикл. Используйте только асинхронные аналоги.
asyncio.run и asyncio.create_task
import asyncio
async def download(url: str, delay: float) -> str:
await asyncio.sleep(delay)
return f"Downloaded {url}"
async def main():
# Последовательно — медленно
r1 = await download("url1", 1)
r2 = await download("url2", 1)
# Итого: ~2 секунды
# Параллельно — быстро
task1 = asyncio.create_task(download("url1", 1))
task2 = asyncio.create_task(download("url2", 1))
r1 = await task1
r2 = await task2
# Итого: ~1 секунда
asyncio.run(main())
asyncio.gather и asyncio.wait
import asyncio
async def fetch(n):
await asyncio.sleep(n)
return f"result-{n}"
async def main():
# gather — запуск нескольких корутин, ожидание всех
results = await asyncio.gather(
fetch(1), fetch(2), fetch(3)
)
print(results) # ['result-1', 'result-2', 'result-3']
# wait — больше контроля
tasks = {asyncio.create_task(fetch(i)) for i in range(3)}
done, pending = await asyncio.wait(tasks, timeout=2)
for task in done:
print(task.result())
asyncio.run(main())
aiohttp — асинхронные HTTP-запросы
import aiohttp
import asyncio
async def fetch_url(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
urls = [
"https://python.org",
"https://docs.python.org",
"https://pypi.org",
]
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
results = await asyncio.gather(*tasks)
for url, html in zip(urls, results):
print(f"{url}: {len(html)} chars")
asyncio.run(main())
Не используйте requests в асинхронном коде — он блокирует event loop. Используйте aiohttp или httpx с async-API.
Асинхронные контекстные менеджеры
class AsyncResource:
async def __aenter__(self):
print("Acquiring resource")
await asyncio.sleep(0.1)
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
print("Releasing resource")
await asyncio.sleep(0.1)
async def main():
async with AsyncResource() as resource:
print("Using resource")
Асинхронные генераторы
import asyncio
async def async_range(n):
for i in range(n):
await asyncio.sleep(0.1)
yield i
async def main():
async for value in async_range(5):
print(value)
asyncio.run(main())