You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django REST中如何并行处理图片循环请求与用户注册请求?

解决Django请求阻塞问题:让图片循环请求与注册请求并行运行

Hey there! Let's break down why your requests are blocking each other and how to fix this properly.

问题根源

首先,Django的默认开发服务器(runserver)是单线程的。当你循环调用process_image接口时,每个请求都会占用这个唯一的线程——尤其是video.read()是一个阻塞操作,会让线程一直处于忙碌状态。这时候其他请求(比如用户注册)只能排队等待,直到图片请求停止、线程空闲出来才能被处理。

另外,你的AcceptImages视图里用了全局变量video,这在多线程/异步环境下是线程不安全的,很容易导致奇怪的资源冲突问题,后面我们也会一起解决这个。

解决方案一:用Celery实现异步处理(推荐)

Celery是Python的异步任务队列,专门用来处理这类耗时/阻塞的任务,把它们从web服务器的线程中剥离出来,让web服务器可以专心处理其他请求(比如注册)。下面是针对你的场景的具体实现步骤:

1. 安装依赖

先安装Celery和Redis(作为消息中间件,比RabbitMQ更易上手):

pip install celery redis

2. 配置Celery到你的Django项目

在项目根目录(和settings.py同级)创建celery.py文件:

import os
from celery import Celery

# 设置Django环境变量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '你的项目名称.settings')

app = Celery('你的项目名称')
# 从Django配置中读取Celery设置,前缀为CELERY_
app.config_from_object('django.conf:settings', namespace='CELERY')
# 自动发现所有app中的tasks.py
app.autodiscover_tasks()

然后在项目的__init__.py中添加:

from .celery import app as celery_app

__all__ = ('celery_app',)

接着在settings.py末尾添加Celery的配置:

# Celery配置
CELERY_BROKER_URL = 'redis://localhost:6379/0'  # Redis作为消息代理
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'  # 存储任务结果

3. 把图片处理逻辑封装成Celery任务

在你的app目录下创建tasks.py文件,把原来的图片捕获逻辑移到这里:

from celery import shared_task
import cv2
import base64

@shared_task
def capture_image():
    # 注意:不要用全局video!每次任务初始化摄像头,避免线程安全问题
    video = cv2.VideoCapture(0)  # 根据你的实际设备调整参数,比如视频文件路径
    stat, img = video.read()
    video.release()  # 记得释放摄像头资源
    
    if not stat:
        return None  # 捕获失败返回空
    
    retval, buffer_img = cv2.imencode('.jpg', img)
    # 转成字符串返回,方便前端处理
    resdata = base64.b64encode(buffer_img).decode('utf-8')
    return resdata

4. 修改视图,触发异步任务

更新AcceptImages视图,让它不再直接处理图片,而是触发Celery任务并返回任务ID:

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .tasks import capture_image

class AcceptImages(APIView):
    def post(self, request):
        # 异步触发图片捕获任务
        task = capture_image.delay()
        # 返回202 Accepted,告诉前端任务已开始
        return Response({'task_id': task.id}, status=status.HTTP_202_ACCEPTED)

5. 添加获取任务结果的视图

前端需要通过任务ID轮询获取图片结果,所以新增一个视图:

from celery.result import AsyncResult
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status

class GetImageResult(APIView):
    def get(self, request, task_id):
        task = AsyncResult(task_id)
        
        if task.state == 'SUCCESS':
            # 任务成功,返回图片数据
            return Response({'image_data': task.result})
        elif task.state == 'PENDING':
            # 任务还在处理中,返回状态
            return Response({'status': 'Capturing image...'}, status=status.HTTP_202_ACCEPTED)
        else:
            # 任务失败或其他状态
            return Response({'status': f'Task failed with state: {task.state}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

6. 更新URL配置

在urls.py中添加获取结果的路由:

url(r'^process_image/$', views.AcceptImages.as_view(), name='AcceptImage'),
url(r'^image_result/(?P<task_id>[\w-]+)/$', views.GetImageResult.as_view(), name='GetImageResult'),

7. 前端调整

原来的循环调用process_image需要改成:

  1. 每次请求process_image获取task_id
  2. 循环请求image_result/{task_id},直到拿到image_data
  3. 展示图片后,重复步骤1-2继续获取下一张

8. 启动Celery Worker

最后,在终端启动Celery worker来处理异步任务:

celery -A 你的项目名称 worker --loglevel=info

解决方案二:用多线程服务器(快速缓解,不推荐生产环境)

如果暂时不想用Celery,可以用多线程的Django开发服务器,这样可以同时处理多个请求:

python manage.py runserver --threaded

不过这种方法只是缓解,因为video.read()的阻塞还是会占用线程,如果循环请求太频繁,还是会出现排队问题。而且全局video的线程安全问题依然存在,生产环境建议还是用Celery+专业服务器(比如Gunicorn+Nginx)。

关键注意事项

  • 不要用全局变量video:在多线程/异步环境下,全局变量会被多个请求/任务共享,导致摄像头资源冲突,必须在每个任务/请求中单独初始化并释放。
  • 生产环境部署:生产环境不要用runserver,要用Gunicorn或uWSGI配合多worker,再加上Celery处理异步任务,这样才能保证高并发和稳定性。

内容的提问来源于stack exchange,提问作者Muhammad Nabeel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:45:21