使用 Django 和 uvicorn 异步提供文件

Serving file asyncronously with Django and uvicorn

我有一个提供文件内容的 Django 视图。直到最近,Django 应用程序还是 运行 WSGI。这很好用。然后我调整我的应用程序以使用 ASGI 运行 uvicorn。文件服务现在已中断,因为它似乎失去了连接。

如何使用 Django 和 uvicorn 异步提供文件?

当前视图:

class FileServeView(View):
    def get(self, request, *args, **kwargs):
        # ...
        return HttpResponse(
            FileWrapper(file_content), content_type="application/octet-stream"
        )

服务器抛出以下错误:

ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/uvicorn/protocols/http/h11_impl.py", line 377, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/usr/local/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
    return await self.app(scope, receive, send)
  File "/usr/local/lib/python3.8/site-packages/uvicorn/middleware/asgi2.py", line 17, in __call__
    await instance(receive, send)
  File "/usr/local/lib/python3.8/site-packages/channels/http.py", line 192, in __call__
    await self.handle(body_stream)
  File "/usr/local/lib/python3.8/site-packages/asgiref/sync.py", line 382, in __call__
    raise RuntimeError(
RuntimeError: Single thread executor already being used, would deadlock

我正在使用:

Django                        3.2.13
uvicorn                       0.17.1
channels                      2.3.1
asgiref                       3.5.0

我无法用你的例子重现,但这个问题似乎与你的问题有关:channels#1722

勾选 dr-luk response:

As @fmgoncalves has mentioned, one way is to alter how the files are served, but I have found a little more reliable patch that I have implemented in my Django Channels Setup based on the information provided by their post.

It seems that the change of thread_sensitive=False to default to True is causing these deadlock detection messages. As far as I can see, Django Channels doesn't seem to mind the occurrences of the deadlocks.

That being said, I felt it would be safe to monkey patch the sync_to_async function for my Django installation.

project_app/moneky_patches.py

from asgiref.sync import sync_to_async

def patch_sync_to_async(*args, **kwargs):
  """
  Monkey Patch the sync_to_async decorator
  ---------------------------------------
  ASGIRef made a change in their defaults that has caused major problems
  for channels. The decorator below needs to be updated to use
  thread_sensitive=False, thats why we are patching it on our side for now.
  https://github.com/django/channels/blob/main/channels/http.py#L220
  """
  kwargs['thread_sensitive'] = False
  return sync_to_async(*args, **kwargs)

Then you just overwrite the existing instance of asgiref's sync_to_async method with our patched wrapper that enforces thread_sensitive=False

project_app/__init__.py

from . import monkey_patches
import asgiref

### Monkey Patches
asgiref.sync.sync_to_async = monkey_patches.patch_sync_to_async

This is make channels, and all of Django run in a insensitive manner like it did before the ASGIRef update.