2

I'm trying to build a little python application, where a simple webserver runs in the background, and you can the use GUI to send different messages.

I'm using PyQt5 and Python3.6, and I've managed to pass data from the working thread to the GUI, but I don't know how to do that the other way around.

Here's a skeleton of my code:

MainWindow:

class Ui_MainWindow(object):
    def __init__(self):
        super().__init__()

        self.input = True
        # 1 - create Worker and Thread inside the Form
        self.obj = WebServer.WebServer(self.input)  # no parent!
        self.thread = QtCore.QThread()  # no parent!
        # 2 - Connect Worker`s Signals to Form method slots to post data.
        self.obj.dataReady.connect(self.onDataReady)
        # 3 - Move the Worker object to the Thread object
        self.obj.moveToThread(self.thread)
        # 4 - Connect Worker Signals to the Thread slots
        self.obj.finished.connect(self.thread.quit)
        # 5 - Connect Thread started signal to Worker operational slot method
        self.thread.started.connect(self.obj.startServer)
        # 6 - Start the thread
        self.thread.start()
        # 7 - Start the form
        self.setupUi()

    def setupUi(self): 
        # Set up the GUI
        #...

        self.MainWindow.show()

    def onDataReady(self, data):
        # Data received from working thread
        # Do stuff...

    def on_click_set2(self):
        self.input = not self.input
        print(self.input)

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    ui = Ui_MainWindow()
    sys.exit(app.exec_())

WebServer:

class WebServer(QObject):
    finished = pyqtSignal()
    dataReady = pyqtSignal(dict)

    def __init__(self, input):
        super().__init__()
        self.input = input

    @pyqtSlot()
    def startServer(self): # A slot takes no params
        # self.loop = asyncio.get_event_loop()
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)
        coro = asyncio.start_server(self.handle_update, '192.168.2.1', 8888, loop=self.loop)
        self.server = self.loop.run_until_complete(coro)

        # Serve requests until Ctrl+C is pressed
        print('Serving on {}'.format(self.server.sockets[0].getsockname()))
        try:
            self.loop.run_forever()
        except KeyboardInterrupt:
            pass

        self.finished.emit()

    async def handle_update(self, reader, writer):
        data = await reader.read(100)
        message = data.decode()
        addr = writer.get_extra_info('peername')
        print(f'Received: {message} from {addr}')

        if self.input:
            print("Input is True")
        else:
            print("Input is False")

        reply = self.input

        print(f'Send: {reply}')
        writer.write(str(reply).encode())
        await writer.drain()

        print("Close the client socket")
        writer.close()
        self.dataReady.emit(reply)

So for example I want to pass input to the thread, and if I do like above (obviously) input always stays the initial value, and won't change in the thread when I hit the button on the GUI.

How can I do it, that the value of the input is updated whenever I hit the button (so passing a value to the thread from GUI during runtime)? I assume is similar to passing from the thread to the GUI, so emitting a signal from GUI and connecting to it on the tread, but I don't know how to find a reference to the GUI from the working thread.

So any advice on how to do that? And of course any other input regarding the code or approach to the application/background server solution is welcomed! Thanks for the help in advance!

UPDATE:

Maybe it wasn't clear what my question was, so here it is:

How can I send a value from the GUI thread to the worker thread while both of them are running parallel? (If the above code makes any sense to you use that as an example, otherwise a general example would be appreciated)

2
  • It shows all the GUI code but it is impossible to analyze where the problem is Commented Jun 20, 2019 at 9:19
  • The problem with the code is that the value of input variable passed to the thread when it's created, but it's not updated during runtime. But I updated my post emphasizing my question. Commented Jun 20, 2019 at 11:01

1 Answer 1

4

It is not necessary that the WebServer live in another thread, it is only necessary that the eventloop be executed in another thread. In this case, the WebServer exchanges the data with the server thread through a queue. Although the QObjects are not thread-safe, the signals are so, there is no problem in emitting the signal from a thread other than the one that the QObject lives

import asyncio
import threading
import queue
from functools import partial
from PyQt5 import QtCore, QtGui, QtWidgets


class WebServer(QtCore.QObject):
    dataReady = QtCore.pyqtSignal(object)

    def startServer(self):
        self.m_loop = asyncio.new_event_loop()
        self.m_queue = queue.Queue()
        asyncio.set_event_loop(self.m_loop)
        coro = asyncio.start_server(
            self.handle_update, "127.0.0.1", 10000, loop=self.m_loop
        )
        self.server = self.m_loop.run_until_complete(coro)
        print("Serving on {}".format(self.server.sockets[0].getsockname()))
        threading.Thread(target=self.m_loop.run_forever, daemon=True).start()

    @QtCore.pyqtSlot(object)
    def setData(self, data):
        if hasattr(self, "m_queue"):
            self.m_queue.put(data)

    def stop(self):
        if hasattr(self, "m_loop"):
            self.m_loop.stop()

    async def handle_update(self, reader, writer):
        reply = ""
        data = await reader.read(100)
        message = data.decode()
        addr = writer.get_extra_info("peername")
        print(f"Received: {message} from {addr}")
        if not self.m_queue.empty():
            data = self.m_queue.get(block=False)
            reply = data
        print(f"Send: {reply}")
        writer.write(str(reply).encode())
        await writer.drain()

        print("Close the client socket")
        writer.close()
        self.dataReady.emit(reply)


class Widget(QtWidgets.QWidget):
    dataChanged = QtCore.pyqtSignal(object)

    def __init__(self, parent=None):
        super().__init__(parent)

        self.m_lineedit = QtWidgets.QLineEdit()
        button = QtWidgets.QPushButton("Send", clicked=self.onClicked)

        lay = QtWidgets.QVBoxLayout(self)
        lay.addWidget(self.m_lineedit)
        lay.addWidget(button)

        self.m_web = WebServer()
        self.m_web.startServer()
        self.dataChanged.connect(self.m_web.setData)

    @QtCore.pyqtSlot()
    def onClicked(self):
        text = self.m_lineedit.text()
        self.dataChanged.emit(text)

    @QtCore.pyqtSlot(object)
    def onDataReady(self, data):
        print(data)

    def closeEvent(self, event):
        self.m_web.stop()
        super().closeEvent(event)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    w = Widget()
    w.show()
    sys.exit(app.exec_())
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.