首页 > 解决方案 > 如何从不同的线程访问 MainThread 元素?

问题描述

我正在使用 PyQt5 在 Python 中创建服务器-客户端 GUI 应用程序。每当新客户端连接到服务器时,我都需要在服务器端显示客户端详细信息。我正在为套接字使用单独的线程。每当我调用该client_connect函数以在服务器端添加小部件时,由于未显示哪个小部件而出现错误。我认为由于 GUI 和套接字代码处于不同的线程中,因此我收到了错误。

QObject::setParent:无法设置父级,新父级在不同的线程中

QObject::installEventFilter():无法过滤不同线程中对象的事件。

QBasicTimer::start: QBasicTimer 只能用于以 QThread 启动的线程

QBasicTimer::start: QBasicTimer 只能用于以 QThread 启动的线程

QBasicTimer::start: QBasicTimer 只能用于以 QThread 启动的线程

QObject::startTimer:定时器只能用于以 QThread 启动的线程

QBasicTimer::start: QBasicTimer 只能用于以 QThread 启动的线程

主功能

if __name__ == "__main__":
    thread1 = threading.Thread(target = ui.server_socket)
    thread1.start()

client_connect 函数- 我为小部件创建了一个单独的文件,并将小部件插入到 tableWidget 中。如果我直接调用该函数,它可以工作,但如果我从套接字代码调用它,它会给我错误。

def client_connect(self,clientid):
            
            self.clientCount = self.clientCount + 1
            self.clientList.append(clientid)
            self.clientDict[clientid] = QtWidgets.QWidget()
            self.clientDict[clientid].ui = clients()
            self.clientDict[clientid].ui.setupUi(self.clientDict[clientid])
            self.clientDict[clientid].ui.label_clientid.setText(str(clientid))
            self.tableWidget_client.setRowCount(self.clientCount)
            self.tableWidget_client.setCellWidget(self.clientCount-1,0,self.clientDict[clientid])

套接字编程

 def start_socket(self):
        self.ssock.listen(20)
        while True:
            conn, c_addr = self.ssock.accept()
            print(conn,c_addr)

            thread2 = threading.Thread(target=self.handle_client, args=(conn,c_addr))
            thread2.start()

    def handle_client(self, conn, c_addr):
        try:
            self.c = conn.recv(1024).decode(self.FORMAT)

            thread3 = threading.Thread(target = self.client_connect, args = (self.c_id,))
            thread3.start()
           
        except socket.error as e:
            print(e)

        

标签: pythonmultithreadingsocketspyqt5

解决方案


正如您所怀疑的,您不应该直接从不同的线程修改 PyQt 对象。您可以阅读有关线程和对象的文档。文档特别提到:

如果您在不存在于当前线程中的 QObject 子类上调用函数并且该对象可能会接收事件,则必须使用互斥锁保护对 QObject 子类内部数据的所有访问;否则,您可能会遇到崩溃或其他不良行为。[.......] 另一方面,您可以安全地从 QThread::run() 实现中发出信号,因为信号发出是线程安全的。

PyQt 希望你这样做的方式是使用 Signals 和 Slots。下面是一个从 worker 的 run 函数发出信号的小例子。

class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        # Some Ui stuff is happening here
        self.worker = Worker(self.startParm)  
        self.worker.client_connected.connect(self.display_client_info)     
        self.worker.start() # When you want to process a new client connection

    def display_client_info(self, client_id):
        # Code here should be similar to what you're trying to do in client_connect

class Worker(QThread):
    def __init__(self):
        super(Worker, self).__init__()
        self.client_connected = pyqtSignal(int)

    def run(self, clientid):
        # The code you want to be executed in a thread
        # For example whatever happens in handle_client, except in your design you're going
        # through a middle thread and this is a basic example.
        # Eventually emit client_connected signal with the client id:
        self.client_connected.emit(client_id)

从中得到的主要概念:

  • 仅从主线程修改 PyQt 对象
  • 创建信号并将它们连接到将在主线程中为您更新 UI 的函数
  • 需要时从线程内发出信号

覆盖 QThread 的 run 函数是一种更简单但可能会受到限制的使用方式。您可以考虑创建 QObjects 并使用moveToThread函数获取更多选项。


推荐阅读