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注册回调函数,任务完成后自动触发后续逻辑。
关键检查点
- 发送消息方法要匹配Consumer类型:
- 异步Consumer必须用
await self.send(...) - 同步Consumer直接用
self.send(...)
- 异步Consumer必须用
- 同步Consumer中禁止直接
await异步函数,必须用任务+回调的方式避免阻塞事件循环。
内容的提问来源于stack exchange,提问作者Niklas
相关产品推荐
相关产品推荐

