В этом видео мы продолжим изучать фреймворк asyncio, и мы поговорим о самых важных кирпичиках, которые вы будете использовать для написания программ с использованием библиотеки asyncio. Мы обсудим в этом видео, что такое asyncio.Future, поговорим о том, как создавать объекты типа asyncio.Task. Также мы рассмотрим проблему запуска синхронных функций в цикле обработки событий и немного обсудим библиотеки, которые существуют уже для работы с фреймворком asyncio. Давайте перейдем к примеру. На слайде показан пример функции, которая называется slow_operation — это наша корутина, которую мы объявили. Мы в нее передаем некий объект future. asyncio.Future — это такой объект, который исполняется, и его выполнение еще не завершено. Этот объект, целиком и полностью, его интерфейс соответствует объекту concurrent.futures.Future. Мы разбирали пример работы с этим объектом, когда знакомились с потоками, и исполняли код при помощи ThreadPoolExecutor объекта. Давайте разберем, что делается в этом примере. Мы объявили некую функцию, передали в нее наш созданный объект future, выполнили sleep на одну секунду, и после эи после этого при помощи set_result выставили результат в наш объект типа future. Обратите внимание - в основной программе мы создаем этот объект, далее мы создаем нашу корутину создаем нашу корутину при помощи ensure_future, а в основном цикле обработки событий мы ожидаем завершения нашего объекта future, не этой функции, которая называется slow_operation, а именно нашего объекта future. Таким образом, при помощи объектов класса future можно выстраивать цепочки не только из двух объектов, но и более сложные цепочки, и очень удобно дожидаться завершения выполнения всех объектов. event loop asyncio сам исполнит нужный код и вернет нам результаты. Давайте посмотрим, как работает пример в консоли. Переключимся в консоль. Для этого нам понадобится наш пример. Так он выглядит. Давайте запустим его при помощи команды python3. Как мы видим, наша функция успешно отработала, и мы дождались выполнения нашей созданной future, или нашего созданного объекта класса Future. Давайте рассмотрим следующий пример и посмотрим, как можно запустить несколько корутин в одном event loop. Для этого, как правило, используется объект типа asyncio.Task. asyncio.Task является наследником класса asyncio.Future, и у него существует ряд дополнительных методов. Со всеми этими методами вы можете ознакомиться в документации, но мы рассмотрим лишь базовый принцип того, как создавать таски и как с ними работать. Итак, напрямую объект типа asyncio.Task создавать не нужно. Нужно использовать метод create_task из вашего event loop и передавать в него корутину. То есть, у каждого объекта типа Task есть собственная корутина, которую он внутри исполняет. Итак, мы создаем список из двух тасков. Запоминаем его в виде списка объектов, и далее при помощи метода asyncio.wait мы исполняем список наших тасков в нашем event loop. Обратите внимание, что можно исполнять как список тасков, так и отдельный таск. Например, если мы исполним код, который я сейчас выделил, то не нужно никаких дополнительных функций вида asyncio.wait. Также существует более удобная обертка для исполнения списка тасков — это asyncio.gather. Давайте рассмотрим, как этот пример будет работать на самом деле в консоли. Переключимся в консоль. Итак, нам нужен код. Давайте исполним его и посмотрим, как на самом деле работает наша корутина. Запускаем при помощи команды python3. Обратите внимание на то, какой вывод сейчас у нас на экране присутствует. Хочу обратить ваше внимание, что у нас внутри корутины работают последовательно, но все эти корутины исполняются одновременно. Мы видим, что у нас сначала один таск выполняет нулевую итерацию, затем второй таск с номером 1 выполняет свою нулевую итерацию. И затем по очереди нулевой таск выполняет первую итерацию, первый таск выполняет первую итерацию, то есть наши функции, наши корутины, исполняются в event loop'е одновременно, а код при этом мы пишем последовательный. Это очень удобно, код выглядит достаточно просто. Он похож на исполнение потоков, но на самом деле он выполняется последовательно. Давайте еще раз переключимся в консоль и посмотрим на различные методы работы с тасками в asyncio. Так, нам еще раз понадобится наш пример. Запускаем интерпретатор python3. Давайте запустим отдельный таск в нашем event loop. Делается это достаточно просто - выполняем метод loop.create_task, тем самым создавая таск и добавляя его в наш event loop, и далее выполняем метод run_until_complete. Этот вызов должен нам вернуть результаты выполнения нашего таска. Да, действительно, мы видим результат выполнения нашего таска — это число 3. Давайте попробуем выполнить несколько тасков при помощи удобной функции asyncio.gather. Делается это точно так же — мы запускаем loop.run_until_complete, передаем туда удобную функцию asyncio.gather и в этой функции перечисляем список тасков. Хочу обратить внимание, в чем отличие. Нам не нужно здесь вызывать метод create_task, все внутри будет автоматически вызвано, не нужно запоминать список наших тасков. Вся эта конструкция вернет результаты выполнения. Давайте проверим. Итак, ожидаем завершения наших тасков. Мы опять видим, что таски хоть и работают последовательно, но запускаются они одновременно и пока один таск выполняет sleep, второй продолжает работать и так далее. Итак, мы получили ответ. И, действительно, это массив, который содержит результаты работы двух наших тасков. Давайте двигаться дальше, и мне хотелось бы остановить ваше внимание на том, как исполнить синхронную функцию в нашем event loop. Как правило, таких задач не должно возникать, но если вдруг они и возникли, то они будут представлять из себя небольшую сложность. Хочу объяснить, почему. Так как наш event loop, или цикл обработки событий, постоянно переключает контекст, и переключает контекст между всеми нашими корутинами и их исполняет последовательно, пока одна корутина ожидает ввода-вывода, вторую корутину наш event loop благополучно исполняет. Если код, который будет исполняться в корутине, будет блокирующим, то наш event loop не сможет делать переключения контекста, и это будет очень плохо сказываться на вообще всем нашем коде, и с этим нужно что-то делать. Как раз для этого в asyncio существует метод run_in_executor. Он означает запустить код буквально в пуле потоков, который внутри этого event loop'а автоматически будет создан. Можно использовать и собственный пул потоков, а можно использовать уже готовый по умолчанию. Более подробную информацию можно получить в документации, а на этом примере можно наблюдать, как функция urlopen, которая открывает некий url, который ей передали по http, скачивает результаты в отдельном потоке и мы дожидаемся результатов выполнения этой функции, опять же при помощи механизма event loop, который называется futures. Давайте посмотрим, как он выполняется. Итак, мы вызываем метод run_in_executor. Здесь внутри будет создано нужное количество потоков, и наша функция sync_get_url с параметром url будет выполнена в отдельном потоке. Для того, чтобы дождаться результата выполнения нашей функции в отдельном потоке, мы используем конструкцию await и передаем туда объект future. Давайте посмотрим, как этот код будет работать в консоли. Итак, нам нужен код нашего примера. Давайте еще раз на него взглянем. Мы будем открывать страничку google.com и выводить на экран количество байт, которые она занимает. Итак, выполняем наш пример. Мы видим на экране 11 килобайт. Мы исполнили сейчас функцию urlopen в отдельном потоке, и в нашем event loop мы дождались результатов выполнения этой функции, которая работала синхронно. Как правило, такие задачи будут возникать у вас, если в некоторой библиотеке не будет поддержки работы с asyncio. Это может сказаться негативно, как я уже сказал, на производительности, потому что, как мы помним, потоки в Python запускаются и работают с ограничением GIL, и будет лучше, если ваш код будет работать без этих вещей. Хочу остановить ваше внимание на том, что сейчас уже есть достаточно большое количество библиотек, которые работают с asyncio, где можно посмотреть документацию по ним и получить информацию, как их использовать. Я привел ссылку на слайде, это на github.com/aio-libs. Там существует достаточно большой список этих библиотек. Самая известная библиотека — это aiohttp. При помощи нее можно делать http запросы в вашем коде и выполнять эти запросы без ThreadPoolExecutor'а, а напрямую в корутинах. aiomysql — это библиотека для работы с популярной базой mysql, aiomcache, и так далее. Таких библиотек появляется в последнее время все больше и больше, и это радует, так как все это обеспечит хорошее качество написания кода для работы с библиотекой asyncio. Итак, в этом видео мы рассмотрели основные приемы для написания кода с использованием библиотеки asyncio. Мы поговорили о том, что такое asyncio.Future объект. Мы поговорили о том, как можно запустить несколько объектов типа asyncio.Task и посмотреть, как они исполняются в event loop'е, а также обсудили проблему работы с синхронными функциями, какие приемы для этого использовать. Все эти подходы и проблемы, которые мы обсудили, понадобятся вам в дальнейшем для написания заданий и самостоятельного изучения библиотеки asyncio.