Reputation: 341
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)
Upvotes: 2
Views: 1144
Reputation: 243897
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_())
Upvotes: 4