将 ZMQ 事件循环与 QT/Pyforms 事件循环相结合

combining ZMQ event loop with QT / Pyforms event loop

我正在尝试同时实现 zmq 和 Pyforms GUI,它们都需要自己的事件循环。任务是拥有一个带有文本字段的 Pyforms GUI,用于显示传入的 zmq 消息。 这是我要开始工作的简化代码。

import pyforms
from   pyforms          import BaseWidget
from   pyforms.controls import ControlTextArea
from   pyforms.controls import ControlButton
import threading
import zmq
from zmq.asyncio import Context
from zmq.eventloop.zmqstream import ZMQStream
from zmq.eventloop import ioloop


class SimpleExample1(BaseWidget):

    def __init__(self):
        super(SimpleExample1,self).__init__('Simple example 1')

        #Definition of the forms fields
        self._controltextarea     = ControlTextArea('textarea to show incoming zmq messages')
        self._button        = ControlButton('Press this button')

        def echo(msg):
            self._controltextarea.__add__(msg) #this should add a line in the Textbox with the message "msg"


        context = Context.instance()
        s = context.socket(zmq.PULL)
        s.connect('tcp://127.0.0.1:5014')
        stream = ZMQStream(s)
        stream.on_recv(echo)  #this calls the function echo from the zmq Ioloop when something is recived

#Execute the application
if __name__ == "__main__":
    #here is where I have tried a lot to make both loops work simultaniously, without success
    guiThread = threading.Thread(target=pyforms.start_app( SimpleExample1 ))
    zmqThread = threading.Thread(target=lambda: ioloop.IOLoop.current().start())
    zmqThread.setDaemon(True)
    guiThread.start()
    zmqThread.start()

这是 ZMQ 发件人。

import zmq
import time

context = zmq.Context()
publisher = context.socket(zmq.PUSH)
publisher.bind('tcp://127.0.0.1:5014')

while True:
    publisher.send_string('something')
    #print('sended')
    time.sleep(1)

我看到了 2 种可能的解决方案。 首先,它可以像上面的代码一样使用线程。但是我还没有找到启动两个事件循环的方法。当我不使用 lamda 等时,其中一个语句会阻塞另一个语句,或者我会收到错误消息。或者它只是不工作。 - 这是我尝试为此实现但没有成功的参考,描述了类似的任务:github maartenbreddels

第二种方案是将echo()的zmq函数调用添加到Pyforms的eventloop中(据我所知是基于QT的)。这可能是最优雅的,但我不知道如何实现或向 GUI 的事件循环添加一些东西。

我已经为这两种解决方案尝试了很多但都没有成功。
我能找到的最有价值的信息在这里:

pyzmq readthedocs

zeromq org

pyforms readthedocs

我没有太多经验,尝试理解诸如 futures、promises 和协程之类的东西,还有像 asyncio、green in python 这样的框架,但到目前为止没有成功。收到消息后,对 "echo" 的简单函数调用就是我要找的。

任何想法如何让它发挥作用?我在做什么傻事吗?

提前为模糊的答案道歉,但也许它可以作为一个潜在的起点。

PyForms 看起来,最终,它是基于 Qt 的。我认为 Qt 可以使用套接字(嗯,文件描述符)作为输入甚至源。 ZeroMQ,至少是 C 版本,公开了一个文件描述符,当接收到 ZMQ 消息时,该文件描述符变为可读状态。所以原则上,Qt 可以使用这个文件描述符来调用一个回调来读取任何 ZMQ 套接字已经收到的消息,并在 Qt 事件循环的线程上处理消息(这可能还有其他好处!)。

恐怕我不知道 PyZMQ 和 PyForms 是否公开了这些内容。

感谢 Bazza 的意见。您的回答帮助我找到了解决问题的方法。在搜索了如何发出 Qevent 之后;我找到了下面的例子 example 并解决了问题。 最终代码如下所示:

import pyforms
from   pyforms          import BaseWidget
from   pyforms.controls import ControlTextArea
from   pyforms.controls import ControlButton
import threading
import zmq
from PyQt5 import QtCore

class ZeroMQ_Listener(QtCore.QObject):

    message = QtCore.pyqtSignal(str)

    def __init__(self):

        QtCore.QObject.__init__(self)

        # Socket to talk to server
        context = zmq.Context()
        self.socket = context.socket(zmq.PULL)
        self.socket.connect('tcp://127.0.0.1:5014')
        print('connected!')
        self.running = True

    def loop(self):
        while self.running:
            string = self.socket.recv_string()
            self.message.emit(string)


class SimpleExample1(BaseWidget):

    def __init__(self):
        super(SimpleExample1,self).__init__('Simple example 1')

        #Definition of the forms fields
        self._controltextarea     = ControlTextArea('textarea to show incoming zmq messages')
        self._button        = ControlButton('Press this button')

        message = QtCore.pyqtSignal(str)
        self.thread = QtCore.QThread()
        self.zeromq_listener = ZeroMQ_Listener()

        self.zeromq_listener.moveToThread(self.thread)

        self.thread.started.connect(self.zeromq_listener.loop)
        self.zeromq_listener.message.connect(self.signal_received)

        QtCore.QTimer.singleShot(0, self.thread.start)


    def signal_received(self, message):
        self._controltextarea.__add__(message)

#Execute the application
if __name__ == "__main__":
    guiThread = threading.Thread(target=pyforms.start_app( SimpleExample1 ))

    guiThread.start()

非常感谢和最诚挚的问候!