避免“两个事件循环试图一次在一个通道层上接收!” Django频道错误

问题描述

简而言之:我正在尝试使Django通道使用者与单独的逻辑线程进行通信。

不管我在下面问什么,除了Django-Channels之外,我可能还需要考虑一个更合适的选项。我的目标是:

  • 我想以编程方式启动逻辑线程,这就是为什么Django-Channels工作线程对我而言并不真正的原因。
  • 我希望逻辑线程能够将数据发送给使用者
  • 我希望消费者能够将数据发送到逻辑线程
  • 我不想在(例如)“主机”使用者中处理游戏逻辑,因为如果主机必须重新连接其Websocket,游戏将会崩溃

更长的版本:假设我正在尝试在网站上制作一个简单的游戏,该网站使用websocket在前端和后端之间进行通信。使用Django Channels,可以很容易地进行设置。

但是,当我需要处理游戏逻辑时,我想在某个中央线程中进行计算,只要客户端断开连接,该线程就不会停止。我发现您可以通过使用者范围here之外的任意线程与使用者进行通信。但是,这有一个问题:

  • 游戏逻辑可以轻松地将信息发送给客户端,但是客户端无法将信息发送回去。
  • 如果我们要使用RESTful API来让客户端将信息发送到游戏逻辑线程,那将会很慢,并且我们无法利用consumer.disconnect()来彻底处理客户端断开连接。
  • 当尝试在游戏逻辑线程中使用async_to_sync(self.channel_layer.receive)(...)时,将导致以下错误Two event loops are trying to receive() on one channel layer at once!很明显,RedisChannelLayer并没有享受监听事件的单独线程。

因此,我认为我需要找到另一种方法,以某种方式将信息从使用者发送到其他线程。换句话说,在以下代码段中:

class GameClientConsumer(JsonWebsocketConsumer):
    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)

def connect(self):
    # Accept the websocket connection.
    self.accept()

def receive_json(self,content=None,**kwargs):
    # Handle player input
    send_@R_225_4045@ion_to_another_thread(data)

...我需要找到send_@R_225_4045@ion_to_another_thread(data)的样子。这有可能吗?另外,我想找出如何通过任意线程从通道层进行receive()事件。

解决方法

根据您的解释,您似乎需要两件事:

  1. 从使用者外部向通道层发送消息。正如您所说的,您已经在文档中找到了。

  2. 将来自使用者的消息发送到另一个线程或代码块。使用者代码与其他任何Python代码一样,因此您可以从中调用任意函数。当然,您应注意不要阻塞事件循环,因此应在使用者外部将繁琐的逻辑分配给另一个进程或线程。例如,您可以将外部逻辑放在celery任务中,如果它很重,就可以像调用其他celery任务一样简单地调用该任务。本质上:

    def receive_json(self,content=None,**kwargs):
        # Handle player input
        celery_task.delay(**kwargs)
    

通过这种方式,任务在您每次收到新消息时运行。如果您有许多这样的任务,则可以在收到新消息时触发Django信号,然后处理程序可以决定如何处理它(触发celery任务,运行小的逻辑并返回,然后将一些消息发送回消费者等)

您不需要通过通道层调用接收函数。而是将消息从接收功能推送到需要它们的服务。