Передача данных в поток из GUI во время выполнения в Python - PullRequest
1 голос
/ 20 июня 2019

Я пытаюсь создать небольшое приложение на Python, где простой веб-сервер работает в фоновом режиме, и вы можете использовать графический интерфейс для отправки различных сообщений.

Я использую PyQt5 и Python3.6,и мне удалось передать данные из рабочего потока в графический интерфейс, но я не знаю, как это сделать наоборот.

Вот скелет моего кода:

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)

Так, например, я хочу передать ввод в поток, и если я делаю как выше (очевидно), ввод всегда остается начальным значением и не изменится в потоке, когда я нажму кнопку в графическом интерфейсе.

Как я могу сделать так, чтобы значение ввода обновлялось всякий раз, когда я нажимал кнопку (поэтому передача значения потоку из GUI во время выполнения)?Я предполагаю, что это похоже на переход от потока к графическому интерфейсу, поэтому излучение сигнала из графического интерфейса и подключение к нему по протектору, но я не знаю, как найти ссылку на графический интерфейс из рабочего потока.

Так какой совет, как это сделать?И, конечно, приветствуется любой другой вклад, касающийся кода или подхода к решению сервера приложений / фона!Заранее спасибо за помощь!

ОБНОВЛЕНИЕ:

Может быть, не совсем понятно, каков был мой вопрос, поэтому вот он:

Как я могу отправитьзначение из потока GUI в рабочий поток, в то время как оба они работают параллельно? (Если приведенный выше код имеет какой-либо смысл для вас, используйте это в качестве примера, иначе будет полезен общий пример)

1 Ответ

3 голосов
/ 20 июня 2019

Нет необходимости, чтобы WebServer находился в другом потоке, нужно только, чтобы цикл обработки событий выполнялся в другом потоке.В этом случае WebServer обменивается данными с потоком сервера через очередь.Хотя объекты QObject не являются поточно-ориентированными, сигналы таковы, но нет проблем с излучением сигнала из потока, отличного от того, в котором живет объект QObject

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_())
...