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

Django Websocket问题:循环结束后才发送所有倒计时消息

Django Websocket倒计时消息发送问题解决方案

问题核心

直接await倒计时函数时消息批量延迟发送,放入asyncio任务虽能即时发送但无法等待任务完成后执行后续逻辑,本质是事件循环调度与Consumer类型不匹配导致的。

解决方案

方案1:改用AsyncWebsocketConsumer(推荐)

如果当前用的是同步WebsocketConsumer,替换为异步版本,确保所有方法都是async定义,直接await倒计时函数即可兼顾即时发消息和等待任务完成:

from channels.generic.websocket import AsyncWebsocketConsumer
import json
import asyncio

class GameLobbyConsumer(AsyncWebsocketConsumer):
    # 省略connect/disconnect等基础方法

    async def receive(self, text_data=None, bytes_data=None):
        text_data_json = json.loads(text_data)
        if "command" in text_data_json:
            command = text_data_json['command']
            match command:
                case 'start':
                    # 直接await,消息即时发送,同时等待倒计时完成
                    is_success = await self.init_starting()
                    # 这里写倒计时完成后的后续逻辑
                    if is_success:
                        print("倒计时结束,游戏正式启动")
                case _:
                    print("未知命令")

    async def send_status_message_with_status(self, status, data):
        # 异步Consumer必须用await发送消息
        await self.send(text_data=json.dumps({
            'status': status,
            'data': data
        }))

    # 你的倒计时函数保持不变
    async def init_starting(self):
        if not self.lobby.status == "waiting":
            print("游戏已启动")
            return False
        self.lobby.status = 'starting'
        await self.save_lobby()

        await self.send_status_message_with_status(status='starting', data={'has_started': self.lobby.has_started,
                                                                            'host': self.host.username,
                                                                            'players': self.players,
                                                                            'lobby_code': self.lobby_code,
                                                                            'countdown': 3})

        countdown = 3
        while countdown >= 0:
            countdown_data = {'countdown': countdown}
            await self.send_status_message_with_status(status='starting', data=countdown_data)
            countdown -= 1
            await asyncio.sleep(1)

        self.lobby.status = 'started'
        await self.save_lobby()
        await self.send_status_message_with_status(status='started', data={'message': "Test"})
        return True

原理:AsyncWebsocketConsumer运行在asyncio事件循环中,await self.send和await asyncio.sleep(1)会主动让出事件循环,让Channels即时推送消息;同时await init_starting()会阻塞当前流程,直到倒计时全部完成再执行后续代码。

方案2:同步Consumer下用任务+回调

如果无法改用异步Consumer,可通过任务回调实现“即时发消息+等待任务完成”:

from channels.generic.websocket import WebsocketConsumer
import json
import asyncio

class GameLobbyConsumer(WebsocketConsumer):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.start_task = None

    def receive(self, text_data=None, bytes_data=None):
        text_data_json = json.loads(text_data)
        if "command" in text_data_json:
            command = text_data_json['command']
            match command:
                case 'start':
                    loop = asyncio.get_running_loop()
                    self.start_task = loop.create_task(self.init_starting())
                    # 注册任务完成后的回调
                    self.start_task.add_done_callback(self.on_countdown_finish)
                case _:
                    print("未知命令")

    async def init_starting(self):
        # 倒计时函数保持不变,注意发送消息的方法要适配同步Consumer
        if not self.lobby.status == "waiting":
            print("游戏已启动")
            return False
        self.lobby.status = 'starting'
        await self.save_lobby()

        self.send_status_message_with_status(status='starting', data={'has_started': self.lobby.has_started,
                                                                      'host': self.host.username,
                                                                      'players': self.players,
                                                                      'lobby_code': self.lobby_code,
                                                                      'countdown': 3})

        countdown = 3
        while countdown >= 0:
            countdown_data = {'countdown': countdown}
            self.send_status_message_with_status(status='starting', data=countdown_data)
            countdown -= 1
            await asyncio.sleep(1)

        self.lobby.status = 'started'
        await self.save_lobby()
        self.send_status_message_with_status(status='started', data={'message': "Test"})
        return True

    def send_status_message_with_status(self, status, data):
        # 同步Consumer直接调用self.send,无需await
        self.send(text_data=json.dumps({
            'status': status,
            'data': data
        }))

    def on_countdown_finish(self, task):
        # 倒计时完成后的后续逻辑
        result = task.result()
        if result:
            print("倒计时结束,游戏初始化完成")
            # 可在此发送后续通知
            self.send(text_data=json.dumps({
                'status': 'post_start',
                'data': {'message': '游戏已准备就绪'}
            }))

原理:任务在后台事件循环独立运行,确保消息即时发送;通过add_done_callback注册回调函数,任务完成后自动触发后续逻辑。

关键检查点

  1. 发送消息方法要匹配Consumer类型:
    • 异步Consumer必须用await self.send(...)
    • 同步Consumer直接用self.send(...)
  2. 同步Consumer中禁止直接await异步函数,必须用任务+回调的方式避免阻塞事件循环。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 06:05:28