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

Какие знаешь особенности работы с асинхронным программированием?

1.7 Middle🔥 181 комментариев
#Асинхронность и многопоточность

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

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

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

Какие знаешь особенности работы с асинхронным программированием?

Асинхронное программирование (asyncio) — один из самых мощных инструментов в Python для обработки I/O-bound операций. Но оно требует кардинально другого мышления.

1. Основной концепт: Корутины и Event Loop

Корутина — это функция, которая может приостановиться (await), пока ждет I/O, позволяя другим корутинам выполняться.

import asyncio

# Обычная функция
def sync_hello():
    print("Hello")
    time.sleep(1)  # Блокирует ВСЁ
    print("World")

# Асинхронная функция (корутина)
async def async_hello():
    print("Hello")
    await asyncio.sleep(1)  # Не блокирует другие корутины!
    print("World")

# Event Loop — главный диспетчер, который управляет корутинами
async def main():
    # Запустить две корутины параллельно
    await asyncio.gather(
        async_hello(),  # Напечатает в 0s, 1s
        async_hello(),  # Напечатает в 0s, 1s
    )
    # Всего времени: 1 секунда (не 2!)

asyncio.run(main())

# Output:
# Hello
# Hello
# (wait 1 second)
# World
# World

2. Event Loop как сердце асинхронности

Event Loop — это главный цикл, который:

  1. Запускает готовые корутины
  2. Проверяет завершенные операции I/O
  3. Просыпает заснувшие корутины
  4. Повторяет в цикле
import asyncio

async def worker(name, delay):
    print(f"{name} started")
    await asyncio.sleep(delay)  # Корутина спит, но Event Loop работает с другими
    print(f"{name} finished")

async def main():
    # Создаем 3 корутины
    tasks = [
        asyncio.create_task(worker("Task 1", 3)),
        asyncio.create_task(worker("Task 2", 1)),
        asyncio.create_task(worker("Task 3", 2)),
    ]
    
    # Event Loop управляет всеми одновременно
    await asyncio.gather(*tasks)

asyncio.run(main())

# Output (в порядке завершения):
# Task 1 started
# Task 2 started
# Task 3 started
# Task 2 finished  (1s)
# Task 3 finished  (2s)
# Task 1 finished  (3s)
# Total: 3s (а не 6s!)

3. Async/Await синтаксис

async определяет корутину, await останавливает выполнение:

import asyncio
import aiohttp

# Неправильно: обычная функция, не корутина
def fetch_wrong():
    response = requests.get("https://api.example.com")  # БЛОКИРУЕТ!
    return response.json()

# Правильно: асинхронная функция
async def fetch_correct():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://api.example.com") as response:
            return await response.json()  # Не блокирует!

async def main():
    # await может быть только в async функции
    result = await fetch_correct()
    print(result)

asyncio.run(main())

# Ошибка: нельзя использовать await вне async функции
# await fetch_correct()  # SyntaxError!

# Нужно запустить через Event Loop
asyncio.run(fetch_correct())

4. Tasks и Futures

Task — это обертка вокруг корутины, которая позволяет запустить ее асинхронно:

import asyncio

async def fetch_data(url):
    await asyncio.sleep(1)
    return f"Data from {url}"

async def main():
    # Способ 1: await (ждет выполнения)
    result = await fetch_data("https://api.example.com")
    print(result)  # Через 1 секунду
    
    # Способ 2: create_task (запустить и не ждать сразу)
    task1 = asyncio.create_task(fetch_data("https://api1.example.com"))
    task2 = asyncio.create_task(fetch_data("https://api2.example.com"))
    
    print("Tasks created, not waiting yet")
    
    # Позже ждем оба
    result1 = await task1
    result2 = await task2
    print(result1, result2)
    
    # Способ 3: gather (самый удобный для нескольких корутин)
    results = await asyncio.gather(
        fetch_data("https://api1.example.com"),
        fetch_data("https://api2.example.com"),
        fetch_data("https://api3.example.com"),
    )
    print(results)  # Все 3 параллельно за 1 секунду

5. Важная особенность: Зависание (Blocking Calls)

Самая частая ошибка — использование синхронного кода в async функции:

import asyncio
import requests  # Синхронная библиотека
import aiohttp   # Асинхронная библиотека

# НЕПРАВИЛЬНО: это блокирует Event Loop!
async def bad_fetch():
    response = requests.get("https://api.example.com")  # БЛОКИРУЕТ ВСЁ!
    return response.json()

async def main():
    # Хотя это async функция, requests.get заблокирует Event Loop
    # На время выполнения requests.get другие корутины не будут выполняться
    result = await bad_fetch()

# ПРАВИЛЬНО: используй асинхронные версии
async def good_fetch():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://api.example.com") as response:
            return await response.json()

async def better_fetch_with_blocking():
    # Если нужно использовать синхронный код, запусти его в thread pool
    loop = asyncio.get_event_loop()
    result = await loop.run_in_executor(None, requests.get, "https://api.example.com")
    return result.json()

6. Таймауты и Отмена

import asyncio

async def slow_operation():
    await asyncio.sleep(10)
    return "Done"

async def main():
    # Таймаут: отмени задачу если она не закончится за 2 секунды
    try:
        result = await asyncio.wait_for(slow_operation(), timeout=2)
    except asyncio.TimeoutError:
        print("Operation timed out!")
    
    # Отмена задачи
    task = asyncio.create_task(slow_operation())
    await asyncio.sleep(1)
    task.cancel()  # Отменить задачу
    
    try:
        await task
    except asyncio.CancelledError:
        print("Task was cancelled")

7. Race Conditions в асинхронности

Даже в async коде есть race conditions, но они другого типа:

import asyncio

counter = 0

async def increment_bad():
    """Имеет race condition, даже в async!"""
    global counter
    # Эти строки НЕ атомарные в Python:
    # 1. Прочитать counter
    # 2. Добавить 1
    # 3. Записать обратно
    # Другая корутина может выполниться между ними!
    counter += 1

async def main():
    tasks = [increment_bad() for _ in range(100)]
    await asyncio.gather(*tasks)
    print(f"Counter: {counter}")  # < 100 (race condition!)

asyncio.run(main())

# Правильно: используй Lock
lock = asyncio.Lock()

async def increment_good():
    global counter
    async with lock:  # Гарантирует эксклюзивный доступ
        counter += 1

async def main():
    counter = 0
    tasks = [increment_good() for _ in range(100)]
    await asyncio.gather(*tasks)
    print(f"Counter: {counter}")  # = 100

8. Иерархия корутин и исключения

Исключения в корутинах требуют особого внимания:

import asyncio

async def failing_task():
    await asyncio.sleep(0.1)
    raise ValueError("Something went wrong!")

async def main():
    # Если не обработать исключение, оно будет проигнорировано!
    task = asyncio.create_task(failing_task())
    
    # Другой код продолжит выполняться
    await asyncio.sleep(0.2)
    
    # Исключение выбросится только при await
    try:
        await task
    except ValueError as e:
        print(f"Caught: {e}")
    
    # Правильно: обработать исключение в gather
    try:
        await asyncio.gather(
            failing_task(),
            asyncio.sleep(1),
        )
    except ValueError as e:
        print(f"Error in gather: {e}")

9. Практический пример: Параллельные HTTP запросы

import asyncio
import aiohttp

async def fetch_url(session, url):
    """Асинхронно получить содержимое URL"""
    async with session.get(url) as response:
        return await response.text()

async def fetch_multiple(urls):
    """Получить несколько URL параллельно"""
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, url) for url in urls]
        return await asyncio.gather(*tasks)

async def main():
    urls = [
        "https://example.com",
        "https://google.com",
        "https://github.com",
    ]
    
    results = await fetch_multiple(urls)
    for url, content in zip(urls, results):
        print(f"{url}: {len(content)} bytes")

asyncio.run(main())

# Output:
# https://example.com: 1256 bytes
# https://google.com: 12453 bytes
# https://github.com: 54321 bytes
# Total time: ~1 second (параллельно!)
# Если последовательно: ~3 seconds

10. Таблица Async vs Sync

АспектSyncAsync
Простота✅ Проста❌ Сложнее
I/O-bound❌ Медленно✅ Быстро
CPU-bound✅ Окей❌ Не подходит
КонкурентностьДо ~100 потоковТысячи задач
СинхронизацияLock, RLockasyncio.Lock
Библиотекиrequests, pandasaiohttp, asyncpg
Debug✅ Проще❌ Сложнее

11. Частые ошибки

# ❌ Ошибка 1: забыли await
async def main():
    task = fetch_url("https://example.com")  # Не выполнится!
    # Нужно: await fetch_url(...)

# ❌ Ошибка 2: смешать sync и async
async def main():
    # requests — синхронный, блокирует Event Loop
    response = requests.get("https://example.com")  # Плохо!
    # Нужно: async with aiohttp.ClientSession() ...

# ❌ Ошибка 3: забыли asyncio.create_task для параллельного выполнения
async def main():
    result1 = await fetch_url("url1")  # Ждет 1
    result2 = await fetch_url("url2")  # Ждет 2 (последовательно!)
    # Нужно:
    task1 = asyncio.create_task(fetch_url("url1"))
    task2 = asyncio.create_task(fetch_url("url2"))
    r1, r2 = await asyncio.gather(task1, task2)  # Параллельно!

# ❌ Ошибка 4: callback hell с .then()
# asyncio не имеет promise-style .then(), используй await вместо этого
Какие знаешь особенности работы с асинхронным программированием? | PrepBro