Как правильно вызывать синхронный код из асинхронной функции в Python

Ответ

Да, это возможно, но требует правильного подхода, чтобы не блокировать цикл событий (event loop), что является главной проблемой при смешивании синхронного и асинхронного кода.

Существует два основных сценария:

1. Для быстрых, неблокирующих операций (CPU-bound)

Если синхронная функция выполняется быстро и не ожидает операций ввода-вывода (I/O), ее можно вызывать напрямую. Это не нанесет вреда производительности.

import asyncio

def fast_sync_operation(a, b):
    # Быстрые вычисления без ожидания
    return a + b

async def main():
    result = fast_sync_operation(5, 10)
    print(f"Результат: {result}")
    await asyncio.sleep(1) # Другая асинхронная работа

asyncio.run(main())

2. Для долгих, блокирующих операций (I/O-bound)

Если функция выполняет долгие операции (например, работа с файлами, запросы к БД через синхронную библиотеку, тяжелые вычисления), ее нужно запускать в отдельном потоке, чтобы не замораживать event loop. Для этого используется loop.run_in_executor().

import asyncio
import time

def slow_blocking_io():
    print("Начало долгой операции...")
    time.sleep(2) # Имитация блокирующей операции (например, чтение файла)
    print("...долгая операция завершена")
    return "Данные получены"

async def main():
    loop = asyncio.get_running_loop()
    print("Запускаем блокирующую задачу без остановки event loop")

    # Запуск синхронной функции в пуле потоков по умолчанию
    result = await loop.run_in_executor(None, slow_blocking_io)

    print(f"Результат из executor: {result}")

asyncio.run(main())

Ключевое правило: Никогда не вызывайте блокирующие I/O операции напрямую в асинхронном коде. Всегда используйте run_in_executor или ищите асинхронные аналоги библиотек.

Ответ 18+ 🔞

Ага, слушай, вот тебе на самом деле интересный вопрос, как эту синхронную хрень в асинхронный код запихнуть, чтобы всё не накрылось медным тазом. Главная засада — это твой event loop, который должен крутиться, как белка в колесе, а не вставать колом, пока какая-нибудь синхронная функция ебёт мозг процессору.

Так, смотри, есть два основных пути, куда свернуть.

Первый — если операция быстрая и не ебёт I/O.

Ну, типа сложить два числа или там строку преобразовать. Если она мгновенная, то вызывай её прямо так, похуй. Цикл событий даже не успеет чихнуть.

import asyncio

def fast_sync_operation(a, b):
    # Быстрые вычисления без ожидания
    return a + b

async def main():
    result = fast_sync_operation(5, 10)
    print(f"Результат: {result}")
    await asyncio.sleep(1) # Другая асинхронная работа

asyncio.run(main())

Вот тут всё просто, блядь. Никакой магии.

А вот второй сценарий — это когда у тебя операция долгая, блокирующая, ёпта.

Ну, представь: чтение здоровенного файла, запрос к какой-нибудь старой библиотеке для БД, которая только синхронная, или там тяжёлые вычисления. Если ты её вызовешь прямо в корутине — всё, пидарас, event loop встанет, как вкопанный, и все остальные задачи будут ждать, пока этот урод закончит. Волнение ебать!

Для такого дела надо юзать loop.run_in_executor(). Он эту синхронную херню запустит в отдельном потоке, и твой главный цикл сможет дальше спокойно жить своей жизнью.

import asyncio
import time

def slow_blocking_io():
    print("Начало долгой операции...")
    time.sleep(2) # Имитация блокирующей операции (например, чтение файла)
    print("...долгая операция завершена")
    return "Данные получены"

async def main():
    loop = asyncio.get_running_loop()
    print("Запускаем блокирующую задачу без остановки event loop")

    # Запуск синхронной функции в пуле потоков по умолчанию
    result = await loop.run_in_executor(None, slow_blocking_io)

    print(f"Результат из executor: {result}")

asyncio.run(main())

Видишь? Всё честно. Сказали "запусти это где-нибудь в сторонке" и спокойно ждём результата с await.

И главное правило, которое надо выжечь себе на подкорке, блядь: никогда не вызывай блокирующие I/O операции напрямую в асинхронном коде. Никогда! Либо через run_in_executor прогоняй, либо ищи асинхронные аналоги библиотек. Иначе получишь performance пиздец, и все будут тебя ненавидеть. Всё.