ASGI 规范、帮助代码和适配器
项目描述
ASGI 是 Python 异步 Web 应用程序和服务器相互通信的标准,被定位为 WSGI 的异步继承者。您可以在https://asgi.readthedocs.io/en/latest/阅读更多内容
该软件包包括 ASGI 基础库,例如:
同步到异步和异步到同步函数包装器,asgiref.sync
服务器基类,asgiref.server
WSGI-to-ASGI 适配器,在asgiref.wsgi中
函数包装器
这些允许您包装或装饰异步或同步函数以从其他样式调用它们(因此您可以从同步线程调用异步函数,反之亦然)。
尤其是:
AsyncToSync 让同步子线程在主线程的事件循环上调用异步函数时停止并等待,然后在异步函数完成时将控制权返回给线程。
SyncToAsync 让异步代码调用同步函数,该函数在线程池中运行,并在同步函数完成时将控制权返回给异步协程。
这个想法是让从异步代码调用同步 API 和从同步代码调用异步 API 变得更容易,因此更容易将代码从一种风格转换到另一种风格。在 Channels 的情况下,我们用 SyncToAsync 包装(同步)Django 视图系统,以允许它在(异步)ASGI 服务器内运行。
请注意,运行的具体线程是非常具体的,旨在保持与旧同步代码的最大兼容性。有关完整说明,请参阅下面的“同步代码和线程”。出于安全考虑,默认情况下, sync_to_async会将程序中的所有同步代码运行在同一个线程中;您可以使用@sync_to_async(thread_sensitive=False)禁用此功能以获得更高的性能 ,但请确保您的代码在执行时不依赖任何绑定到线程(如数据库连接)的内容。
线程局部替换
这是threading.local的替代品,适用于线程和异步任务。更好的是,当您使用sync_to_async 在线程池中运行事物时,它会将值从任务本地上下文代理到线程本地上下文,反之亦然async_to_sync。
如果您想要真正的线程和任务安全,则可以在 Local 对象上设置 thread_critical以确保这一点。
服务器基类
包括一个StatelessServer类,它提供了编写无状态服务器的所有艰苦工作(例如,不处理直接传入的套接字,而是使用外部流或套接字来计算正在发生的事情)。
这种服务器的一个例子是聊天机器人服务器,它连接到中央聊天服务器,并为每个与之聊天的用户提供“连接范围”。只有一个实际连接,但服务器必须将事物分成几个范围,以便于编写代码。
您可以在frequensgi中看到一个使用此示例的示例。
WSGI 到 ASGI 适配器
允许您包装 WSGI 应用程序,使其显示为有效的 ASGI 应用程序。
简单地将它包裹在您的 WSGI 应用程序中,如下所示:
asgi_application = WsgiToAsgi(wsgi_application)
WSGI 应用程序将在同步线程池中运行,并且包装好的 ASGI 应用程序将是一个接受http类消息的应用程序。
请注意,并非 WSGI 的所有扩展功能都受支持(例如传入 POST 主体的文件句柄)。
依赖项
asgiref需要 Python 3.7 或更高版本。
贡献
请参阅 主要频道贡献文档。
测试
要运行测试,请确保您已使用软件包额外安装了测试:
cd asgiref/ pip install -e .[tests] pytest
构建文档
该文档使用Sphinx:
cd asgiref/docs/ pip install sphinx
要构建文档,您可以使用默认工具:
sphinx-build -b html . _build/html # or `make html`, if you've got make set up cd _build/html python -m http.server
…或者您可以使用sphinx-autobuild运行服务器并自动重建/重新加载您的文档更改:
pip install sphinx-autobuild sphinx-autobuild . _build/html
释放
要发布,首先将详细信息添加到 CHANGELOG.txt 并更新asgiref/__init__.py中的版本号。
然后,构建并推送包:
python -m build twine upload dist/* rm -r build/ dist/
实施细节
同步代码和线程
asgiref.sync模块提供了两个包装器,让您可以随意在异步和同步代码之间切换,同时为您处理粗糙的边缘。
不幸的是,粗糙的边缘很多,并且代码必须特别努力地工作以尽可能地将事物保持在同一个线程中。值得注意的是,我们正在处理的限制是:
所有通过SyncToAsync 调用并标有 thread_sensitive的同步代码应该彼此运行在同一个线程中(如果程序的外层是同步的,则主线程)
如果一个线程已经有一个正在运行的异步循环,那么如果AsyncToSync在调用堆栈中位于您上方的同步代码上被阻塞,则它无法在该循环上运行。
你得到的第一个妥协可能是thread_sensitive代码应该只在同一个线程中运行而不是在子线程中产生,满足第一个限制,但这会立即让你进入第二个限制。
唯一真正的解决方案是本质上具有 ThreadPoolExecutor 的变体,它在最外层同步线程(主线程或单个衍生的子线程)上执行任何thread_sensitive代码。
这意味着您现在有两个基本状态:
如果你的程序的最外层是同步的,那么所有通过AsyncToSync运行的异步代码将在任意子线程中的每次调用事件循环中运行,而所有线程敏感代码将在主线程中运行。
如果你程序的最外层是异步的,那么所有异步代码都运行在主线程的事件循环中,所有线程敏感的同步代码都将运行在单个共享子线程中。
至关重要的是,这意味着在这两种情况下,都有一个线程是所有线程敏感代码都必须在其上运行的共享资源,并且该线程当前有可能在其自己的AsyncToSync调用上被阻塞。因此, AsyncToSync在阻塞时需要充当线程代码的执行器。
CurrentThreadExecutor类提供了这个功能;您可以调用它的run_until_future方法,而不是简单地等待 Future,它会运行提交的代码,直到 Future 完成。这意味着调用中的代码可以在您的线程上运行代码。
维护和安全
要报告安全问题,请联系security @ djangoproject 。com。有关 GPG 签名和更多安全过程信息,请参阅 https://docs.djangoproject.com/en/dev/internals/security/。
要报告错误或请求新功能,请打开一个新的 GitHub 问题。
此存储库是 Channels 项目的一部分。对于牧羊人和维护团队,请参阅 主要频道自述文件。