У каждого объекта задачи есть метод cancel, который можно вызвать, если требуется остановить задачу. В результате снятия задача возбудит исключение CancelledError, когда мы ждем ее с помощью await. Пример в файле cancelled_task
В asyncio есть возможность передать отслеживание в виде функции asyncio.wait_for. Она принимает объект сопрограммы или задачи и тайм-аут в секундах и возвращает сопрограмму, к которой можно применить await. Если задача не завершилась в отведенное время, то возбуждается исключение TimeoutError и задача автоматически снимается. Пример в файле wait_forАвтоматическое снятие задачи, работающей дольше, чем ожидается, обычно является разумной практикой. В противном случае сопрограмма могла бы ждать неопределенно долго, занимая ресурсы, которые никогда не будут освобождены.
Для этого обернем нашу задачу функцией asyncio.shield. Эта функция предотвращает снятие сопрограммы, снабжая ее «щитом», позволяющим игнорировать запросы на снятие. Пример в файле shield_exampleРассмотрим пример – future_object – отправка веб-запроса возвращает объект future. В этом примере случае future возвращается немедленно, но, поскольку запрос занимает некоторое время, значение future еще не определено. Позже, когда запрос завершится, результат будет установлен, и мы сможем его получить.
Между задачами и будущими объектами существует тесная связь. На самом деле task напрямую наследует future. Можно считать, что объект future представляет значение, которое появится только в будущем. А task является комбинацией сопрограммы и future. Создавая задачу, мы создаем пустой объект future и запускаем сопрограмму. А когда сопрограмма завершится с результатом или вследствие исключения, мы записываем этот результат или объект-исключение во future.
Связующим звеном между задачами и сопрограммами является абстрактный базовый класс Awaitable. Любой объект, который реализует метод await, можно использовать в выражении await. Сопрограммы, как и будущие объекты, наследуют Awaitable напрямую, а задачи же расширяют будущие объекты.
Это поможет составить четкое представление о том, какой выигрыш в производительности дает конкурентность. Нужно внимательно следить за тем, чтобы не попасть в одну из типичных ловушек, которые могут не повысить, а снизить производительность приложения. Есть две основные ошибки на пути преобразования приложения в асинхронное. Первая – попытка выполнить счетный код в задачах или сопрограммах, не прибегая к многопроцессности, вторая – использовать блокирующие API ввода-вывода, пренебрегая многопоточностью.Большинство API, с которыми мы обычно работаем, в настоящее время являются блокирующими и без доработок работать с asyncio не будут. Нужно использовать библиотеку, которая поддерживает сопрограммы и неблокирующие сокеты. А это значит, что если используемая вами библиотека не возвращает сопрограммы и вы не употребляете await в собственных сопрограммах, то, вероятно, совершаете блокирующий вызов. В примере выше мы могли бы использовать библиотеку aiohttp, в которой используются неблокирующие сокеты и которая возвращает сопрограммы, тогда с конкурентностью все было бы нормально.
Если нужно сделать что-то нестандартное, например работать с сокетами напрямую или запланировать задачу на конкретный момент в будущем, то доступ к циклу событий необходим. Существует еще функция asyncio.get_event_loop, также позволяющая получить доступ к циклу событий. Эта функция может создать новый цикл событий, если его еще не существует в момент вызова, что ведет к странному поведению. Рекомендуется использовать get_running_ loop, поскольку она возбуждает исключение, если цикл событий не запущен, что позволяет избежать сюрпризов. Функция asyncio.run, которой мы пользовались для выполнения сопрограмм, имеет именованный параметр debug. По умолчанию он равен False, но если присвоить ему значение True, то активируется отладочный режим: asyncio.run(coroutine(), debug=True)Включить отладочный режим можно, передав аргумент -X dev в командной строке, запускающей Python-приложение: python3 -X dev program.py
Включить отладочный режим можно также, присвоив значение 1 переменной окружения: PYTHONASYINCIODEBUG=1 python3 program.py
Это может быть полезно для отладки ошибок, связанных со случайным выполнением блокирующего вызова. По умолчанию параметры заданы так, что предупреждение выдается, если сопрограмма работает дольше 100 мс, но, возможно, для вас это слишком мало или слишком много. Чтобы изменить значение, нужно получить объект цикла событий и задать в нем параметр slow_callback_duration, как в debug_asyncio Это число с плавающей точкой равно количеству секунд, при превышении которого обратный вызов считается медленным.