run_in_executor
KTSКогда пригодится
Тема сегодняшнего поста будет полезна, если в асинхронном коде вам вдруг позарез понадобилось вызвать синхронную операцию — или выполнить тяжелую cpu bound операцию.
Подробнее о синхронных операциях
Выделяют 2 основных типа синхронных операций: CPU и IO bound.
IO Bound — операция, скорость выполнения которой ограничена скоростью подсистемы ввода-вывода. К таким задачам можно отнести выполнение запросов по сети, операции с базой, чтение/запись файла на диск.


CPU Bound — операция, скорость выполнения которой ограничена скоростью CPU. Задача с вычислением наборов чисел — например, умножение матриц.

Вообще, синхронный подход считается традиционным в программировании. Операции при этом полностью блокируют поток других задач на время своего выполнения. Это время может достигать десятков секунд, а то и больше.
Задача
Теперь посмотрим на примеры задач, когда вы программируете асинхронно, но понадобилось вызвать блокирующую синхронную операцию:
IO-операция: нужно подключиться к системе, у которой нет асинхронной библиотеки.
CPU-операция: нужно пройтись по всем ключам большого json в несколько мегабайт, который получили от внешнего сервиса.
Решение
Запускать синхронные io bound или cpu bound операции нужно не в event loop, а в отдельном потоке/процессе.
И вот мы добрались до героя поста: в asyncio есть механизм асинхронного запуска операции в потоке/процессе: run_in_executor.
Его синтаксис можно посмотреть здесь: asyncio.loop.run_in_executor
Что и для какой операции использовать
Для выполнения CPU bound операций стоит использовать run_in_executor c ProcessPoolExecutor. CPU bound операции выполняются в другом процессе и не блокируют event loop.
Для выполнения IO bound операций стоит использовать run_in_executor c ThreadPoolExecutor. Синхронные IO bound операции «отпускают» GIL, и во время выполнения могут выполняться другие потоки — то есть наш event loop.