Это Кросс-пост из моего блога. Если вам нравится мой контент, вы можете подписаться через Электронная почта или RSS Анкет
Начиная с v2.0 Флэста добавила асинхронные представления которые позволяют использовать Асинхронизация
и ждет
В рамках функции просмотра. Это позволяет вам использовать другие ASYNC API при создании веб -приложения с помощью колбы.
Если вы планируете использовать асинхронные представления Flask, есть внимание, чтобы узнать об использовании глобально определенных клиентов API или приспособлений, которые являются асинхронными.
Создание приложения Async Flask
В этом примере мы используем Async Elasticsearch Python Client как наш глобальный матч. Мы инициализируем простое приложение Flask с одним асинхронным представлением, который делает запрос с Async Elasticsearch Client:
# app.py from flask import Flask, jsonify from elasticsearch import AsyncElasticsearch app = Flask(__name__) # Create the AsyncElasticsearch instance in the global scope. es = AsyncElasticsearch( "https://localhost:9200", api_key="..." ) @app.route("/", methods=["GET"]) async def async_view(): return jsonify(**(await es.info()))
Бег с Онломщик
через $ umericorn App: App
а затем посещение приложения через http://localhost: 8000
Анкет После первого запроса все выглядит нормально:
{ "cluster_name": "d31d9d6abb334a398210484d7ac8567b", "cluster_uuid": "K5uyniiMT9u2grNBmsSt_Q", "name": "instance-0000000001", "tagline": "You Know, for Search", "version": { "build_date": "2021-04-20T20:56:39.040728659Z", "build_flavor": "default", "build_hash": "3186837139b9c6b6d23c3200870651f10d3343b7", "build_snapshot": false, "build_type": "docker", "lucene_version": "8.8.0", "minimum_index_compatibility_version": "6.0.0-beta1", "minimum_wire_compatibility_version": "6.8.0", "number": "7.13.1" } }
Однако, когда вы обновляете страницу, чтобы отправить второй запрос, вы получите InternalError
и следующий след:
Traceback (most recent call last): ... File "/app/app.py", line 13, in async_route return jsonify(**(await es.info())) File "/app/venv/lib/...", line 288, in info return await self.transport.perform_request( File "/app/venv/lib/...", line 327, in perform_request raise e File "/app/venv/lib/...", line 296, in perform_request status, headers, data = await connection.perform_request( File "/app/venv/lib/...", line 312, in perform_request raise ConnectionError("N/A", str(e), e) ConnectionError(Event loop is closed) caused by: RuntimeError(Event loop is closed)
Почему это происходит?
В сообщении об ошибке упоминается, что цикл события закрыт, а? Чтобы понять, почему это происходит, вам нужно знать, как Asyncelasticsearch
реализован и как работают асинхронные взгляды в колбе.
Нет глобальных циклов событий
Async Code опирается на то, что называется цикла событий. Итак, любой код, использующий Асинхронизация
или ждет
Не могу выполнить без цикла событий, который «работает». К сожалению, когда вы запускаете Python (т. Е. Глобальный объем).
Вот почему вы не можете иметь код, который выглядит так:
async def f(): print("I'm async!") # Can't do this! await f()
Вместо этого вы должны использовать asyncio.run ()
и обычно Асинхронизация
Основная/входная функция для использования Ждите
вот так:
import asyncio async def f(): print("I'm async!") async def main(): await f() # asyncio starts an event loop here: asyncio.run(main())
(Есть исключение из этого через python -m asyncio
/ Ipython
, но на самом деле это запускает реплику после начала цикла событий)
Поэтому, если вам нужен цикл событий для запуска любого асинхрового кода, как вы можете определить Asyncelasticsearch
экземпляр в глобальной области?
Как Asynclasticsearch позволяет глобальные определения
Магия глобальных определений для Asyncelasticsearch
задерживает полную инициализацию вызова asyncio.get_running_loop ()
, Создание aiohttp. Сессия
, нюхание и т. Д. До тех пор, пока мы не получили наш первый Асинхронизация
вызов. Однажды Асинхронизация
Звонок сделан, мы можем почти гарантировать, что есть цикл бега, потому что, если не было никакого цикла, которое в любом случае не сработает.
Особенно это отлично подходит для асинхронных программ, поскольку обычно используется один цикл событий на протяжении всего выполнения программы и означает, что вы можете создать свой Asyncelasticsearch
экземпляр в глобальном объеме, как пользователи создают свои синхронные Elasticsearch
Клиент в глобальном масштабе.
Использование нескольких петлей событий сложно и, вероятно, нарушит многие другие библиотеки, такие как aiohttp
В процессе NO (?) Преимущество, поэтому мы не поддерживаем эту конфигурацию. Теперь, как это разрывается при использовании с новыми асинхронными представлениями Фласки?
Новый цикл событий по асинхронному запросу
Простое объяснение состоит в том, что Flask использует WSGI для обслуживания HTTP -запросов и ответов, которые не поддерживают асинхронный ввод -вывод. Асинхронный код требует выполнения цикла, поэтому Flask необходимо получить цикл с запущенным событием откуда -то, чтобы выполнить асинхронное представление.
Для этого Flask создаст новый цикл событий и начнет запустить представление в этом новом цикле событий для каждого выполнения асинхронного представления. Это означает, что все асинхронные и ожидающие вызовы в представлении увидят тот же цикл событий, но любой другой запрос до или после этого представления увидит другой цикл событий.
Проблема возникает, когда вы хотите использовать асинхронные приспособления, которые находятся в глобальном масштабе, что, по моему опыту, часто встречается в приложениях для малых и средних колбок. Очень неудачная ситуация! Так что мы можем сделать?
Исправление проблемы
Проблема не в Flask или клиенте Python Elasticsearch, проблема в том, что несовместимость между WSGI и асинхронные глобалы. Есть несколько решений, оба из которых связаны с Интерфейс шлюза Async Server (ASGI) , Двоюродный брат WSGI со вкусом асинк, который был разработан с учетом асинхронных программ.
Используйте фреймворк и сервер ASGI
Один из способов полностью избежать проблемы с WSGI – просто использовать нативную структуру веб -приложения ASGI. Есть несколько популярных и широко используемых фреймворков ASGI, которые вы можете выбрать:
Если вы ищете опыт, который очень похож на колбу, вы можете использовать Кварта который вдохновлен Флакой. У кварта даже есть Руководство о том, как мигрировать из приложения колбы на использование кварта ! Собственная документация Flask для асинхронных представлений на самом деле Рекомендуется использовать кварта В некоторых случаях из -за удара производительности с использованием нового цикла событий для запроса.
Если вы хотите узнать что -то новое, вы можете проверить FASTAPI который включает в себя кучу встроенных функций для документирования API, строгих модельных объявлений и проверки данных.
При разработке приложения ASGI, что нужно иметь в виду, вам нужен ASGI-совместимый сервер Анкет Общий выбор включает Uvicorn , Гиперкорн и Дафна Анкет Другой вариант – использовать Онломщик с Uvicorn Workers Анкет
Все упомянутые выше варианты функционируют довольно аналогично, так что выберите тот, который вам нравится. Мой личный выбор исторически был стреляющим из работников Uvicorn из -за того, насколько широко используется и зрелый стрелобник относится к тому, насколько новыми являются другие библиотеки.
Вы можете сделать это так:
$ gunicorn app:app -k uvicorn.workers.UvicornWorker
Используйте wsgitoasgi из Asgiref
Если вы действительно любите Flask и хотите продолжать использовать ее, вы также можете использовать Asgiref Пакет обеспечивает простую обертку под названием Wsgitoasgi
Это преобразует приложение WSGI в приложение ASGI.
from flask import Flask, jsonify from elasticsearch import AsyncElasticsearch # Same definition as above... wsgi_app = Flask(__name__) es = AsyncElasticsearch( "https://localhost:9200", api_key="..." ) @wsgi_app.route("/", methods=["GET"]) async def async_view(): return jsonify(**(await es.info())) # Convert the WSGI application to ASGI from asgiref.wsgi import WsgiToAsgi asgi_app = WsgiToAsgi(wsgi_app)
В этом примере мы преобразуем приложение WSGI wsgi_app
в приложение ASGI asgi_app
Это означает, что когда мы запускаем приложение, для каждого запроса будет использоваться один цикл событий, а не для нового цикла событий по запросу.
Этот подход по-прежнему потребует, чтобы вы использовали ASGI-совместимый сервер.
Оригинал: “https://dev.to/sethmlarson/the-problem-with-flask-async-views-and-async-globals-pl”