Когда следует использовать async, а когда await?
question@mail.ru
·
01.01.1970 03:00
Python когда следует использовать async, а когда await?
answer@mail.ru
·
01.01.1970 03:00
Попытаюсь дополнить ответ @ext.
async/await - это оптимизация, т.е. использовать их категорически не надо там, где все и так работает хорошо. Ведь это усложняет логику исполнения программы и тянет за собой много всего. Потому что если решились использовать асинхронную функциональность - будьте добры использовать ТОЛЬКО библиотеки, которые это поддерживают.
Исполнение асинхронного кода подобно исполнению многопоточного кода на одноядерном процессоре. Так же существуют несколько задач, которые могут быть исполнены одновременно и есть контекст исполнения каждой задачи. Однако при многопоточности процессор сам по таймеру решает, когда переключать контекст и заниматься исполнением другой задачи.
Асинхронный подход предполагает, что в само приложение задает специальные места, где такое переключение возможно. В Python в asyncio этими местами становятся вызовы с await.
Если решились оптимизировать, то в первую очередь стоит вспомнить что такое и чем различаются CPU-bound и IO-bound операции. Первые постоянно задействуют ресурсы процессора, все время что-то считают и вычисляют. Вторые большую часть времени ожидают операций ввода/вывода от файловой системы, сети или еще невесть чего.
Из-за GIL в Python (CPython) CPU-bound операции невозможно оптимизировать с использованием потоков, только вынесением в отдельный процесс. Совершенно то же самое ограничение накладывается на использование асинхронного подхода, потому что асинхронный код преимущественно одноядерный и если один исполнитель будет переключаться с одного процесса на другой, где его присутствие одинаково важно - никакого толку не будет (будет гораздо хуже).
Другое дело IO-bound задачи. Яркими примерами могут служить - чтение файлов, запрос в базу данных, даже time.sleep (который часто используется для имитации сложных IO-bound операций и есть специальный asyncio.sleep).
Например:
def create_report(cursor, *args, **kwargs): # выполнить какую-то подготовительную работу data = cursor.execute(sql_query) # обработать полученные данные и сформировать некий отчетЗдесь функция execute не сильно нагрузит процессор, больше будет ждать ответа от базы данных. Поэтому процессору позволительно заняться в этот момент чем-нибудь другим. Но этот код синхронный, приходится ждать. Если нам надо построить очень много таких отчетов, то мы можем решиться оптимизировать это с использованием asyncio. Давайте попробуем.
Сначала нам придется выбросить старый клиент для похода в базу данных и использовать специальный асинхронный, который будет осуществлять неблокирующий вызов.
async def create_report(cursor, *args, **kwargs): # cursor - это уже не тот cursor, что из предыдущего примера # выполнить какую-то подготовительную работу data = await cursor.execute(sql_query) # обработать полученные данные и сформировать некий отчетЛюбая функция, которая использует await обязана объявляться как async - тут нет выбора. А await объявляет точку в программе, в которой мы можем переключиться на другую полезную работу, если такая есть, потому что ресурсы процессора здесь не нужны. Таким образом мы сможем успеть больше.
Оставляю за скобками инициализацию event-loop и прочие прелести запуска асинхронных программ.
Очень важно - сказал ""А"" (async), говори ""B"" (await), друг без друга они не существуют.
Крайне внятной я нашел следующую информацию:
Не знаю, есть ли в интернете перевод этого добра, но если нужен, я мог бы подготовить.
Напоследок, интересное утверждение: ""используйте асинхронность, когда можете, а потоки - когда должны"".