Учитывая выбор между этими двумя, я определенно рекомендовал бы подход № 1.
# 2 имеет недостаток, который вы упускаете из-за большого количества функций asyncio, разделяя вызовы asyncio на отдельное небольшое событие.цикл проходит.Например, вы не можете создать «фоновую» задачу, выполнение которой охватывает несколько вызовов asyncio.run()
, и такие вещи могут быть очень полезны для регистрации, мониторинга или тайм-аута.(Использование asyncio.run
также может быть проблемой производительности, поскольку при каждом вызове создается новый цикл обработки событий, но это можно исправить, переключившись на run_until_complete
.)
Но есть и третий вариант:
- Создать отдельный поток, который выполняет только
loop.run_forever()
и ожидает выполнения работы.Остальная часть программы состоит из обычного кода блокировки, который может запросить что-то от asyncio, используя asyncio.run_coroutine_threadsafe()
.Эта функция не блокируется;он немедленно возвращает concurrent.futures.Future
, который вы можете передать, а метод result()
автоматически ожидает получения результата.Он поддерживает дополнительные функции, такие как ожидание параллельного завершения нескольких экземпляров с использованием wait
, итератор as_completed
и т. Д.
ЭтоПодход ИМХО сочетает в себе лучшие характеристики двух вариантов из вопроса.Он оставляет блокирующий код по-настоящему блокирующим, при этом ему по-прежнему разрешается ждать, когда что-то произойдет, порождать потоки и т. Д., Не заставляя использовать async def
и run_in_executor
по всем направлениям.В то же время части asyncio могут быть написаны с использованием лучших практик asyncio с длительным циклом обработки событий, обслуживающим всю программу.Вам просто нужно быть осторожным, чтобы all взаимодействовал с циклом событий от остальной части приложения (даже для вызова чего-то столь простого, как loop.stop
), которое должно быть выполнено с помощью loop.call_soon_threadsafe
и asyncio.run_coroutine_threadsafe
.