Asyncio Для Практикующего Разработчика Python

Помню момент, когда я подумал «Как все медленно, а что если распараллелить вызовыЭ», а через 3 дня, глядя на код, я ничего не мог понять в жутком беспорядке потоков, синхронизаторов и коллбэк-функций.

Потом я встретил асинхронный , и все изменилось.

Если кто не знает, asyncio — это новый модуль для организации параллельного программирования, появившийся в Python 3.4. Он предназначен для упрощения использования сопрограмм и фьючерсов в асинхронном коде — чтобы код выглядел синхронным, без обратных вызовов.

Помню, в то время было несколько подобных инструментов, и один из них выделялся — библиотека событие .

Советую всем прочитать это замечательное Руководство по мероприятиям для практикующих разработчиков Python , где описано не только, как с этим работать, но и что такое конкурентоспособность в общем смысле.

Мне настолько понравилась эта статья, что я решил использовать ее как шаблон для написания введения в asyncio. Небольшая оговорка — это не статья о gevent vs asyncio. Натан Роуд уже сделал это для меня в своей примечание .

Все примеры можно найти по адресу GitHub .

Я знаю, что вам не терпится написать код, но сначала я хотел бы рассмотреть несколько концепций, которые пригодятся позже.



Потоки, циклы событий, сопрограммы и фьючерсы

Нитки — самый распространенный инструмент. Я думаю, вы уже слышали об этом раньше, но asyncio оперирует немного другими концепциями: циклами событий, сопрограммами и фьючерсами.

  • цикл событий ( цикл событий ) по большей части просто управляет выполнением различных задач: регистрирует получение и запускает в нужный момент
  • сопрограммы - Ожидаются специальные функции, подобные генераторам Python ( Ждите ), что они передадут управление обратно в цикл событий.

    Необходимо, чтобы они запускались через событийный цикл

  • фьючерсы — объекты, хранящие текущий результат выполнения задачи.

    Это может быть информация о том, что задача еще не обработана или результат уже получен; а может быть, это вообще исключение

Довольно просто? Идти!

Синхронное и асинхронное выполнение

В видео" Параллелизм - это не параллелизм, это лучше Роб Пайк обращает ваше внимание на ключевой момент: разбиение задач на параллельные подзадачи возможно только при наличии параллелизма, который управляет этими подзадачами.

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

Сопрограммы содержат операторы доходности, которые мы используем для определения мест, где мы можем переключиться на другие ожидающие задачи.

За переключение контекста в asyncio отвечает выход, который передает управление обратно в цикл обработки событий, который, в свою очередь, передает другой сопрограмме.

Давайте посмотрим на базовый пример:

  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
   

import asyncio async def foo(): print('Running in foo') await asyncio.sleep(0) print('Explicit context switch to foo again') async def bar(): print('Explicit context to bar') await asyncio.sleep(0) print('Implicit context switch back to bar') ioloop = asyncio.get_event_loop() tasks = [ioloop.create_task(foo()), ioloop.create_task(bar())] wait_tasks = asyncio.wait(tasks) ioloop.run_until_complete(wait_tasks) ioloop.close()



$ python3 1-sync-async-execution-asyncio-await.py Running in foo Explicit context to bar Explicit context switch to foo again Implicit context switch back to bar

* Сначала мы объявили пару простых сопрограмм, которые притворяются неблокирующими, используя спать из асинцио * Сопрограммы могут быть запущены только из другой сопрограммы или заключены в задачу с помощью create_task * Если у нас есть 2 задачи, давайте объединим их, используя ждать * И, наконец, мы отправим его в цикл обработки событий через run_until_complete С использованием Ждите Таким образом, в любой сопрограмме мы заявляем, что сопрограмма может вернуть управление циклу событий, который, в свою очередь, запустит любую из следующих задач: bar. То же самое произойдет и в баре: ожидайте asyncio.sleep управление будет передано обратно в цикл событий, который вернется к выполнению foo в соответствующее время.

Представим себе 2 задачи блокировки: gr1 и gr2, как будто они обращаются к каким-то сторонним сервисам, и пока они ждут ответа, третья функция может работать асинхронно.



import time import asyncio start = time.time() def tic(): return 'at %1.1f seconds' % (time.time() - start) async def gr1(): # Busy waits for a second, but we don't want to stick around. print('gr1 started work: {}'.

format(tic())) await asyncio.sleep(2) print('gr1 ended work: {}'.

format(tic())) async def gr2(): # Busy waits for a second, but we don't want to stick around. print('gr2 started work: {}'.

format(tic())) await asyncio.sleep(2) print('gr2 Ended work: {}'.

format(tic())) async def gr3(): print("Let's do some stuff while the coroutines are blocked, {}".

format(tic())) await asyncio.sleep(1) print("Done!") ioloop = asyncio.get_event_loop() tasks = [ ioloop.create_task(gr1()), ioloop.create_task(gr2()), ioloop.create_task(gr3()) ] ioloop.run_until_complete(asyncio.wait(tasks)) ioloop.close()



$ python3 1b-cooperatively-scheduled-asyncio-await.py gr1 started work: at 0.0 seconds gr2 started work: at 0.0 seconds Lets do some stuff while the coroutines are blocked, at 0.0 seconds Done! gr1 ended work: at 2.0 seconds gr2 Ended work: at 2.0 seconds

Обратите внимание, как работает планирование ввода-вывода и выполнения, позволяющее уместить все это в один поток.

Пока две задачи заблокированы в ожидании ввода-вывода, третья функция может занять все процессорное время.



Порядок выполнения

В синхронном мире мы думаем последовательно.

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

Однако в случае конкуренции в этом нельзя быть уверенным.



import random from time import sleep import asyncio def task(pid): """Synchronous non-deterministic task. """ sleep(random.randint(0, 2) * 0.001) print('Task %s done' % pid) async def task_coro(pid): """Coroutine non-deterministic task """ await asyncio.sleep(random.randint(0, 2) * 0.001) print('Task %s done' % pid) def synchronous(): for i in range(1, 10): task(i) async def asynchronous(): tasks = [asyncio.ensure_future(task_coro(i)) for i in range(1, 10)] await asyncio.wait(tasks) print('Synchronous:') synchronous() ioloop = asyncio.get_event_loop() print('Asynchronous:') ioloop.run_until_complete(asynchronous()) ioloop.close()



$ python3 1c-determinism-sync-async-asyncio-await.py Synchronous: Task 1 done Task 2 done Task 3 done Task 4 done Task 5 done Task 6 done Task 7 done Task 8 done Task 9 done Asynchronous: Task 2 done Task 5 done Task 6 done Task 8 done Task 9 done Task 1 done Task 4 done Task 3 done Task 7 done

Конечно, ваш результат будет другим, поскольку каждая задача будет находиться в режиме ожидания случайное время, но учтите, что результат выполнения совершенно разный, хотя мы всегда размещаем задачи в одном и том же порядке.

Также взгляните на сопрограмму для нашей довольно простой задачи.

Важно понимать, что в asyncio нет никакой магии при реализации неблокирующих задач.

При реализации asyncio стоял отдельно в стандартной библиотеке, потому что остальные модули обеспечивали только функционал блокировки.

Вы можете использовать модуль concurrent.futures чтобы обернуть задачи блокировки в потоки или процессы и получить будущее для использования в asyncio. Несколько таких примеров доступен на GitHub .

Вероятно, это основной недостаток использования asyncio сейчас, но уже существует несколько библиотек, которые помогают решить эту проблему.

Самая популярная задача блокировки — получение данных по HTTP-запросу.

Рассмотрите возможность сотрудничества с отличной библиотекой айоhttp на примере получения информации о публичных мероприятиях на GitHub.

import time import urllib.request import asyncio import aiohttp URL = ' https://api.github.com/events ' MAX_CLIENTS = 3 def fetch_sync(pid): print('Fetch sync process {} started'.

format(pid)) start = time.time() response = urllib.request.urlopen(URL) datetime = response.getheader('Date') print('Process {}: {}, took: {:.

2f} seconds'.

format( pid, datetime, time.time() - start)) return datetime async def fetch_async(pid): print('Fetch async process {} started'.

format(pid)) start = time.time() response = await aiohttp.request('GET', URL) datetime = response.headers.get('Date') print('Process {}: {}, took: {:.

2f} seconds'.

format( pid, datetime, time.time() - start)) response.close() return datetime def synchronous(): start = time.time() for i in range(1, MAX_CLIENTS + 1): fetch_sync(i) print("Process took: {:.

2f} seconds".

format(time.time() - start)) async def asynchronous(): start = time.time() tasks = [asyncio.ensure_future( fetch_async(i)) for i in range(1, MAX_CLIENTS + 1)] await asyncio.wait(tasks) print("Process took: {:.

2f} seconds".

format(time.time() - start)) print('Synchronous:') synchronous() print('Asynchronous:') ioloop = asyncio.get_event_loop() ioloop.run_until_complete(asynchronous()) ioloop.close()



$ python3 1d-async-fetch-from-server-asyncio-await.py Synchronous: Fetch sync process 1 started Process 1: Wed, 17 Feb 2016 13:10:11 GMT, took: 0.54 seconds Fetch sync process 2 started Process 2: Wed, 17 Feb 2016 13:10:11 GMT, took: 0.50 seconds Fetch sync process 3 started Process 3: Wed, 17 Feb 2016 13:10:12 GMT, took: 0.48 seconds Process took: 1.54 seconds Asynchronous: Fetch async process 1 started Fetch async process 2 started Fetch async process 3 started Process 3: Wed, 17 Feb 2016 13:10:12 GMT, took: 0.50 seconds Process 2: Wed, 17 Feb 2016 13:10:12 GMT, took: 0.52 seconds Process 1: Wed, 17 Feb 2016 13:10:12 GMT, took: 0.54 seconds Process took: 0.54 seconds

Здесь есть пара моментов, на которые стоит обратить внимание.

Во-первых, есть разница во времени — при использовании асинхронных вызовов мы выполняем запросы одновременно.

Как говорилось ранее, каждый из них передавал управление следующему и по завершении возвращал результат. То есть скорость выполнения напрямую зависит от времени выполнения самого медленного запроса, который занял всего 0,54 секунды.

Круто, правда? Во-вторых, насколько код похож на синхронный.

По сути это одно и то же! Основные отличия связаны с реализацией библиотеки выполнения запросов, создания и ожидания завершения задач.



Создание конкурентоспособности

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

Однако запуск сопрограмм и получение результатов можно запланировать несколькими способами.

Представьте себе ситуацию, когда нам нужно обрабатывать результаты GET-запросов по мере их получения; по сути реализация очень похожа на предыдущую:

import time import random import asyncio import aiohttp URL = ' https://api.github.com/events ' MAX_CLIENTS = 3 async def fetch_async(pid): start = time.time() sleepy_time = random.randint(2, 5) print('Fetch async process {} started, sleeping for {} seconds'.

format( pid, sleepy_time)) await asyncio.sleep(sleepy_time) response = await aiohttp.request('GET', URL) datetime = response.headers.get('Date') response.close() return 'Process {}: {}, took: {:.

2f} seconds'.

format( pid, datetime, time.time() - start) async def asynchronous(): start = time.time() futures = [fetch_async(i) for i in range(1, MAX_CLIENTS + 1)] for i, future in enumerate(asyncio.as_completed(futures)): result = await future print('{} {}'.

format(">>" * (i + 1), result)) print("Process took: {:.

2f} seconds".

format(time.time() - start)) ioloop = asyncio.get_event_loop() ioloop.run_until_complete(asynchronous()) ioloop.close()



$ python3 2a-async-fetch-from-server-as-completed-asyncio-await.py Fetch async process 1 started, sleeping for 4 seconds Fetch async process 3 started, sleeping for 5 seconds Fetch async process 2 started, sleeping for 3 seconds >> Process 2: Wed, 17 Feb 2016 13:55:19 GMT, took: 3.53 seconds >>>> Process 1: Wed, 17 Feb 2016 13:55:20 GMT, took: 4.49 seconds >>>>>> Process 3: Wed, 17 Feb 2016 13:55:21 GMT, took: 5.48 seconds Process took: 5.48 seconds

Посмотрите на отступы и тайминги — мы запустили все задачи одновременно, но они обрабатываются в том порядке, в котором завершили выполнение.

Код в данном случае немного другой: мы упаковываем в список сопрограммы, каждая из которых уже подготовлена к исполнению.

Функция как_завершено возвращает итератор, который выдает результаты сопрограмм по мере их выполнения.

Круто, правда?! Кстати, и как_завершено , И ждать - функции из пакета concurrent.futures .

Другой пример: что делать, если вы хотите узнать свой IP-адрес.

Сервисов для этого очень много, но вы не знаете, какой из них будет доступен при работе программы.

Вместо последовательного запроса каждого из списка вы можете выполнить все запросы конкурентно и выбрать первый успешный.

Ну и для этого в нашей любимой функции ждать есть специальный параметр return_когда .

До сих пор мы игнорировали то, что возвращает ждать , т.к.

мы просто распараллелили задачи.

Но теперь нам нужно получить результат от сопрограммы, поэтому мы будем использовать набор готовых и ожидающих фьючерсов.



from collections import namedtuple import time import asyncio from concurrent.futures import FIRST_COMPLETED import aiohttp Service = namedtuple('Service', ('name', 'url', 'ip_attr')) SERVICES = ( Service('ipify', ' https://api.ipify.orgЭformat=json ', 'ip'), Service('ip-api', ' http://ip-api.com/json ', 'query') ) async def fetch_ip(service): start = time.time() print('Fetching IP from {}'.

format(service.name)) response = await aiohttp.request('GET', service.url) json_response = await response.json() ip = json_response[service.ip_attr] response.close() return '{} finished with result: {}, took: {:.

2f} seconds'.

format( service.name, ip, time.time() - start) async def asynchronous(): futures = [fetch_ip(service) for service in SERVICES] done, pending = await asyncio.wait( futures, return_when=FIRST_COMPLETED) print(done.pop().

result()) ioloop = asyncio.get_event_loop() ioloop.run_until_complete(asynchronous()) ioloop.close()



$ python3 2c-fetch-first-ip-address-response-await.py Fetching IP from ip-api Fetching IP from ipify ip-api finished with result: 82.34.76.170, took: 0.09 seconds Unclosed client session client_session: <aiohttp.client.ClientSession object at 0x10f95c6d8> Task was destroyed but it is pending! task: <Task pending coro=<fetch_ip() running at 2c-fetch-first-ip-address-response.py:20> wait_for=<Future pending cb=[BaseSelectorEventLoop._sock_connect_done(10)(), Task._wakeup()]>>

Что случилось? Первый сервис ответил успешно, но в логах было какое-то предупреждение! Фактически мы запустили две задачи, но вышли из цикла после первого результата, пока вторая сопрограмма еще работала.

Asyncio подумал, что это ошибка, и предупредил нас.

Наверное, стоит навести порядок за собой и явно отсеять ненужные задачи.

Как? Рад, что вы спросили.



Государства будущего

  • ожидающий
  • бег
  • сделанный
  • отменен
Это так просто.

Когда будущее находится в состоянии выполнения, вы можете получить от него результат выполнения.

В состояниях ожидания и выполнения такая операция приведет к исключению.

Инвалидстатееррор , а в случае отмены это будет Отмененная ошибка и, наконец, если исключение произошло в самой сопрограмме, оно будет выброшено снова (так же, как это было сделано при вызове исключение ).

Но не верьте мне на слово .

Узнать состояние будущего можно с помощью методов сделанный , отменен или бег , но не забывайте, что в случае сделанный вызов результат может возвращать как ожидаемый результат, так и исключение, возникшее во время работы.

Существует метод отмены исполнения будущего отмена .

Это подходит для фиксации нашего примера.



from collections import namedtuple import time import asyncio from concurrent.futures import FIRST_COMPLETED import aiohttp Service = namedtuple('Service', ('name', 'url', 'ip_attr')) SERVICES = ( Service('ipify', ' https://api.ipify.orgЭformat=json ', 'ip'), Service('ip-api', ' http://ip-api.com/json ', 'query') ) async def fetch_ip(service): start = time.time() print('Fetching IP from {}'.

format(service.name)) response = await aiohttp.request('GET', service.url) json_response = await response.json() ip = json_response[service.ip_attr] response.close() return '{} finished with result: {}, took: {:.

2f} seconds'.

format( service.name, ip, time.time() - start) async def asynchronous(): futures = [fetch_ip(service) for service in SERVICES] done, pending = await asyncio.wait( futures, return_when=FIRST_COMPLETED) print(done.pop().

result()) for future in pending: future.cancel() ioloop = asyncio.get_event_loop() ioloop.run_until_complete(asynchronous()) ioloop.close()



$ python3 2c-fetch-first-ip-address-response-no-warning-await.py Fetching IP from ipify Fetching IP from ip-api ip-api finished with result: 82.34.76.170, took: 0.08 seconds

Простой и аккуратный вывод - это именно то, что мне нравится! Если вам нужна какая-то дополнительная логика для обработки фьючерсов, то вы можете подключить коллбэки, которые будут вызываться при входе в состояние Done. Это может быть полезно для тестов, когда некоторые результаты необходимо переопределить с использованием собственных значений.



Обработка исключений

asyncio — это написание управляемого и читаемого параллельного кода, который хорошо виден при обработке исключений.

Давайте вернемся к примеру для демонстрации.

Допустим, мы хотим убедиться, что все запросы к службам определения IP возвращают одинаковый результат. Однако один из них может быть не в сети и не отвечать нам.

Просто примените попытку.

кроме обычного:

from collections import namedtuple import time import asyncio import aiohttp Service = namedtuple('Service', ('name', 'url', 'ip_attr')) SERVICES = ( Service('ipify', ' https://api.ipify.orgЭformat=json ', 'ip'), Service('ip-api', ' http://ip-api.com/json ', 'query'), Service('borken', ' http://no-way-this-is-going-to-work.com/json ', 'ip') ) async def fetch_ip(service): start = time.time() print('Fetching IP from {}'.

format(service.name)) try: response = await aiohttp.request('GET', service.url) except: return '{} is unresponsive'.

format(service.name) json_response = await response.json() ip = json_response[service.ip_attr] response.close() return '{} finished with result: {}, took: {:.

2f} seconds'.

format( service.name, ip, time.time() - start) async def asynchronous(): futures = [fetch_ip(service) for service in SERVICES] done, _ = await asyncio.wait(futures) for future in done: print(future.result()) ioloop = asyncio.get_event_loop() ioloop.run_until_complete(asynchronous()) ioloop.close()



$ python3 3a-fetch-ip-addresses-fail-await.py Fetching IP from ip-api Fetching IP from borken Fetching IP from ipify ip-api finished with result: 85.133.69.250, took: 0.75 seconds ipify finished with result: 85.133.69.250, took: 1.37 seconds borken is unresponsive

Мы также можем обработать исключение, возникшее во время выполнения сопрограммы:

from collections import namedtuple import time import asyncio import aiohttp import traceback Service = namedtuple('Service', ('name', 'url', 'ip_attr')) SERVICES = ( Service('ipify', ' https://api.ipify.orgЭformat=json ', 'ip'), Service('ip-api', ' http://ip-api.com/json ', 'this-is-not-an-attr'), Service('borken', ' http://no-way-this-is-going-to-work.com/json ', 'ip') ) async def fetch_ip(service): start = time.time() print('Fetching IP from {}'.

format(service.name)) try: response = await aiohttp.request('GET', service.url) except: return '{} is unresponsive'.

format(service.name) json_response = await response.json() ip = json_response[service.ip_attr] response.close() return '{} finished with result: {}, took: {:.

2f} seconds'.

format( service.name, ip, time.time() - start) async def asynchronous(): futures = [fetch_ip(service) for service in SERVICES] done, _ = await asyncio.wait(futures) for future in done: try: print(future.result()) except: print("Unexpected error: {}".

format(traceback.format_exc())) ioloop = asyncio.get_event_loop() ioloop.run_until_complete(asynchronous()) ioloop.close()



$ python3 3b-fetch-ip-addresses-future-exceptions-await.py Fetching IP from ipify Fetching IP from borken Fetching IP from ip-api ipify finished with result: 85.133.69.250, took: 0.91 seconds borken is unresponsive Unexpected error: Traceback (most recent call last): File “3b-fetch-ip-addresses-future-exceptions.py”, line 39, in asynchronous print(future.result()) File “3b-fetch-ip-addresses-future-exceptions.py”, line 26, in fetch_ip ip = json_response[service.ip_attr] KeyError: ‘this-is-not-an-attr’

Точно так же, как запуск задачи без ожидания ее завершения является ошибкой, получение неизвестных исключений оставляет свои следы в выводе:

from collections import namedtuple import time import asyncio import aiohttp Service = namedtuple('Service', ('name', 'url', 'ip_attr')) SERVICES = ( Service('ipify', ' https://api.ipify.orgЭformat=json ', 'ip'), Service('ip-api', ' http://ip-api.com/json ', 'this-is-not-an-attr'), Service('borken', ' http://no-way-this-is-going-to-work.com/json ', 'ip') ) async def fetch_ip(service): start = time.time() print('Fetching IP from {}'.

format(service.name)) try: response = await aiohttp.request('GET', service.url) except: print('{} is unresponsive'.

format(service.name)) else: json_response = await response.json() ip = json_response[service.ip_attr] response.close() print('{} finished with result: {}, took: {:.

2f} seconds'.

format( service.name, ip, time.time() - start)) async def asynchronous(): futures = [fetch_ip(service) for service in SERVICES] await asyncio.wait(futures) # intentionally ignore results ioloop = asyncio.get_event_loop() ioloop.run_until_complete(asynchronous()) ioloop.close()



$ python3 3c-fetch-ip-addresses-ignore-exceptions-await.py Fetching IP from ipify Fetching IP from borken Fetching IP from ip-api borken is unresponsive ipify finished with result: 85.133.69.250, took: 0.78 seconds Task exception was never retrieved future: <Task finished coro=<fetch_ip() done, defined at 3c-fetch-ip-addresses-ignore-exceptions.py:15> exception=KeyError(‘this-is-not-an-attr’,)> Traceback (most recent call last): File “3c-fetch-ip-addresses-ignore-exceptions.py”, line 25, in fetch_ip ip = json_response[service.ip_attr] KeyError: ‘this-is-not-an-attr’

Вывод выглядит так же, как и в предыдущем примере, за исключением укоризненного сообщения от asyncio.

Таймауты

Что, если наша информация об IP не так уж важна? Это могло бы стать хорошим дополнением к какому-то составному ответу, в котором эта часть была бы необязательной.

В этом случае мы не будем заставлять пользователя ждать.

В идеале мы бы поставили таймаут для расчета IP, по истечении которого мы в любом случае дали бы пользователю ответ, даже без этой информации.

И опять ждать есть подходящий аргумент:

import time import random import asyncio import aiohttp import argparse from collections import namedtuple from concurrent.futures import FIRST_COMPLETED Service = namedtuple('Service', ('name', 'url', 'ip_attr')) SERVICES = ( Service('ipify', ' https://api.ipify.orgЭformat=json ', 'ip'), Service('ip-api', ' http://ip-api.com/json ', 'query'), ) DEFAULT_TIMEOUT = 0.01 async def fetch_ip(service): start = time.time() print('Fetching IP from {}'.

format(service.name)) await asyncio.sleep(random.randint(1, 3) * 0.1) try: response = await aiohttp.request('GET', service.url) except: return '{} is unresponsive'.

format(service.name) json_response = await response.json() ip = json_response[service.ip_attr] response.close() print('{} finished with result: {}, took: {:.

2f} seconds'.

format( service.name, ip, time.time() - start)) return ip async def asynchronous(timeout): response = { "message": "Result from asynchronous.", "ip": "not available" } futures = [fetch_ip(service) for service in SERVICES] done, pending = await asyncio.wait( futures, timeout=timeout, return_when=FIRST_COMPLETED) for future in pending: future.cancel() for future in done: response["ip"] = future.result() print(response) parser = argparse.ArgumentParser() parser.add_argument( '-t', '--timeout', help='Timeout to use, defaults to {}'.

format(DEFAULT_TIMEOUT), default=DEFAULT_TIMEOUT, type=float) args = parser.parse_args() print("Using a {} timeout".

format(args.timeout)) ioloop = asyncio.get_event_loop() ioloop.run_until_complete(asynchronous(args.timeout)) ioloop.close()

Я также добавил аргумент таймаута в строку запуска скрипта, чтобы проверить, что произойдет, если запросы успеют обработать.

Еще я добавил случайные задержки, чтобы скрипт не заканчивался слишком быстро и было время разобраться, как именно он работает.

$ python 4a-timeout-with-wait-kwarg-await.py Using a 0.01 timeout Fetching IP from ipify Fetching IP from ip-api {‘message’: ‘Result from asynchronous.’, ‘ip’: ‘not available’}



$ python 4a-timeout-with-wait-kwarg-await.py -t 5 Using a 5.0 timeout Fetching IP from ip-api Fetching IP from ipify ipify finished with result: 82.34.76.170, took: 1.24 seconds {'ip': '82.34.76.170', 'message': 'Result from asynchronous.'}



Заключение

Asyncio укрепил мою и без того большую любовь к Python. Честно говоря, я влюбился в сопрограммы, когда впервые обнаружил их в Tornado, но asyncio удалось взять лучшее от нее и других библиотек параллелизма.

Настолько, что были предприняты специальные усилия, чтобы гарантировать, что они смогут использовать основной цикл ввода-вывода.

Итак, если вы используете Торнадо или витой , то вы можете включить код, предназначенный для asyncio! Как я уже упоминал, основная проблема заключается в том, что стандартные библиотеки пока не поддерживают неблокирующее поведение.

Кроме того, многие популярные библиотеки до сих пор работают только в синхронном стиле, а те, которые используют параллелизм, пока еще молоды и экспериментальны.

Однако их число растет .

Надеюсь, в этом уроке я показал, насколько приятно работать с asyncio, и эта технология побудит вас перейти на Python 3, если вы по какой-то причине застряли на Python 2.7. Одно можно сказать наверняка: будущее Python полностью изменилось.

От переводчика: Оригинальная статья была опубликована 20 февраля 2016 года, за это время многое произошло.

Вышел Python 3.6, в котором помимо оптимизаций была улучшена работа asyncio, API переведен в стабильное состояние.

Выпущены библиотеки для работы с Postgres, Redis, Elasticsearch и т. д. в неблокирующем режиме.

Даже новый фреймворк — Sanic, напоминает Flask, но работает в асинхронном режиме.

В конце концов даже цикл событий был оптимизирован и переписан на Cython, что оказалось в 2 раза быстрее.

Так что я не вижу причин игнорировать эту технологию! В опросе могут участвовать только зарегистрированные пользователи.

Войти , Пожалуйста.

Вы используете асинхронно? 29,1% да, и мне это нравится 195 7,61% да, но у меня много проблем 51 56,42% нет, но хотелось бы 378 1,64% нет, даже не думаю об этом 11 5,22% Я в курсе Python 2.7 проголосовали 35 670 пользователей.

128 пользователей воздержались.

Теги: #python #asyncio #примеры #python

Вместе с данным постом часто просматривают: