Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Мои вопросы по корутинам
Как эксперт с 10+ лет опыта, я хотел бы уточнить несколько моментов относительно корутин в Python, которые часто вызывают путаницу даже у опытных разработчиков.
1. Различие между корутинами и asyncio задачами
import asyncio
# Корутина - это объект, результат вызова async функции
async def my_coroutine():
await asyncio.sleep(1)
return "Done"
coro = my_coroutine() # Это корутина
print(type(coro)) # <class coroutine>
# Корутина БЕЗ await просто зависает в памяти
await coro # Нужно await для исполнения
# Задача (Task) - это корутина, обернутая для запуска в event loop
task = asyncio.create_task(my_coroutine()) # Задача
print(type(task)) # <class asyncio.tasks.Task>
# Задача запланирована и выполнится автоматически
await task
Ключевой момент: Задача (Task) - это корутина плюс информация о статусе выполнения.
2. Почему нельзя просто await корутину дважды?
async def my_coroutine():
await asyncio.sleep(1)
return "Done"
coro = my_coroutine()
await coro # OK
await coro # RuntimeError: cannot reuse already awaited coroutine
# Правильно:
coro1 = my_coroutine()
coro2 = my_coroutine()
await asyncio.gather(coro1, coro2)
3. Context variables в async коде
from contextvars import ContextVar
import asyncio
user_id = ContextVar("user_id")
async def process_request(uid):
user_id.set(uid) # Установить для этой корутины
await asyncio.sleep(0.1)
print(f"Processing for user {user_id.get()}") # Видно значение
async def main():
# Каждая корутина имеет собственный context
await asyncio.gather(
process_request(1),
process_request(2),
process_request(3),
)
asyncio.run(main())
# Output:
# Processing for user 1
# Processing for user 2
# Processing for user 3
Важно: Context variables работают правильно в asyncio (не как thread-local, а как корутина-local).
4. Когда использовать Task vs gather vs wait?
import asyncio
async def task1():
await asyncio.sleep(1)
return "task1"
async def task2():
await asyncio.sleep(2)
return "task2"
async def task3():
await asyncio.sleep(0.5)
return "task3"
async def main():
# Вариант 1: create_task - для независимых задач
t1 = asyncio.create_task(task1())
t2 = asyncio.create_task(task2())
# Можно делать другое
print("Tasks created")
# Потом дождаться
results = [await t1, await t2]
# Вариант 2: gather - простой синтаксис
results = await asyncio.gather(task1(), task2(), task3())
# Вариант 3: wait - для сложного управления
done, pending = await asyncio.wait(
[asyncio.create_task(task1()),
asyncio.create_task(task2()),
asyncio.create_task(task3())],
return_when=asyncio.FIRST_COMPLETED
)
for task in done:
print(f"Completed: {task.result()}")
for task in pending:
print(f"Still pending: {task}")
asyncio.run(main())
5. Обработка исключений в async коде
import asyncio
async def failing_task():
await asyncio.sleep(0.5)
raise ValueError("Something went wrong")
async def normal_task():
await asyncio.sleep(0.1)
return "Success"
async def main():
# Способ 1: gather с return_exceptions
results = await asyncio.gather(
normal_task(),
failing_task(),
return_exceptions=True # Возвращает исключение вместо выброса
)
for result in results:
if isinstance(result, Exception):
print(f"Error: {result}")
else:
print(f"Result: {result}")
# Способ 2: try-except для отдельной задачи
try:
await failing_task()
except ValueError as e:
print(f"Caught: {e}")
asyncio.run(main())
6. Отмена задач (cancellation)
import asyncio
async def long_task():
try:
for i in range(100):
print(f"Working: {i}")
await asyncio.sleep(0.1)
except asyncio.CancelledError:
print("Task was cancelled!")
# Очистить ресурсы
raise # Обязательно перебросить CancelledError
async def main():
task = asyncio.create_task(long_task())
await asyncio.sleep(0.5) # Дать задаче поработать
task.cancel() # Отменить задачу
try:
await task
except asyncio.CancelledError:
print("Task cancellation handled")
asyncio.run(main())
7. Таймауты с asyncio
import asyncio
async def slow_operation():
await asyncio.sleep(10)
return "Done"
async def main():
# Способ 1: timeout (Python 3.11+)
try:
async with asyncio.timeout(2):
result = await slow_operation()
except asyncio.TimeoutError:
print("Operation timed out")
# Способ 2: wait_for (старый способ)
try:
result = await asyncio.wait_for(slow_operation(), timeout=2)
except asyncio.TimeoutError:
print("Operation timed out")
asyncio.run(main())
8. Async контекстные менеджеры
import asyncio
class AsyncResource:
async def __aenter__(self):
print("Acquiring resource")
await asyncio.sleep(0.5)
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
print("Releasing resource")
await asyncio.sleep(0.5)
return False
async def do_work(self):
print("Working with resource")
await asyncio.sleep(0.5)
async def main():
async with AsyncResource() as resource:
await resource.do_work()
asyncio.run(main())
9. Async генераторы и async for
import asyncio
async def async_generator():
for i in range(5):
print(f"Generating {i}")
await asyncio.sleep(0.5)
yield i
async def main():
# async for работает с async генераторами
async for value in async_generator():
print(f"Received {value}")
asyncio.run(main())
10. Race conditions в asyncio
import asyncio
shared_counter = 0
async def increment():
global shared_counter
# ❌ RACE CONDITION: несколько корутин могут прочитать одно значение
for _ in range(1000):
temp = shared_counter
await asyncio.sleep(0) # Точка переключения контекста
shared_counter = temp + 1 # Может потеряться обновление
async def main():
await asyncio.gather(increment(), increment(), increment())
print(shared_counter) # Значение < 3000
# ✅ Решение: использовать Lock
shared_counter = 0
lock = asyncio.Lock()
async def increment_safe():
global shared_counter
for _ in range(1000):
async with lock: # Критическая секция
shared_counter += 1
async def main_safe():
await asyncio.gather(
increment_safe(),
increment_safe(),
increment_safe()
)
print(shared_counter) # 3000
asyncio.run(main_safe())
Мои рекомендации как эксперта
- Для I/O-bound операций - asyncio идеален, используй его.
- Для CPU-bound - asyncio НЕ поможет (нет истинного параллелизма).
- Всегда оборачивай корутины в Task если нужна независимая работа.
- Обрабатывай CancelledError правильно при отмене.
- Используй Lock для общих ресурсов - asyncio НЕ гарантирует безопасность без синхронизации.
- Context variables вместо глобальных для хранения данных корутины.
- Профилируй - asyncio может быть медленнее на простых операциях из-за overhead.
Понимание этих деталей критично для написания надежного асинхронного кода.