在使用Python进行多线程时,PyQt GUI会在线程完成之前冻结。

4
代码非常庞大,因此我将只发布一些片段。
class Program(QtGui.QWidget):
    def __init__(self, parent=None):
        super(Program, self).__init__(parent)

...

def main():   
    app = QtGui.QApplication(sys.argv)
    global ex
    ex = Program()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

在这些标签、文本框、按钮和4个QListWidgets之间,我有一堆子布局,这些子布局被添加到网格中。

根据输入,我启动了可变数量的线程。通常是4个线程。它们在自己的类中启动:

class myThread(Thread):
    def __init__(self, arguments, more_arguments):
        threading.Thread.__init__(self)

    def work_it(self, argument, more_arguments):
        Program.some_function_in_Program_class(ex, arguments)

然后我从那里调用Program()类内部的函数来实际改变GUI。

Program.generate_results(ex, arguments, arguments2, more_arguments)

最终,这归结为我遍历的列表,无论是打印每个元素还是使用:
my_listbox.addItem(item)

它会冻结GUI,直到所有4个线程都完成列表的遍历。然后所有结果一起出现,而不是一个接一个地出现。

我在Tkinter中做过这个,我可以看到一个接一个的列表项动态地出现在ListBox小部件中,而不会冻结GUI。

至于线程管理,我的做法是遍历一个列表,并根据其长度创建多个线程:

threadlist = []
for i in self.results:
    sub_thread = myThread(i, self.results[i])
    self.threadlist.append(sub_thread)
swapAndWaitThread = spawnAndWaitThreadsThread(self.threadlist)
swapAndWaitThread.start()

我这样做是为了能够管理这4个线程,并能够知道它们何时完成。
class spawnAndWaitThreadsThread(Thread):
    def __init__(self, threadlist):
        threading.Thread.__init__(self)
        self.threadlist = threadlist

    def run(self):
        for thread in self.threadlist:
            thread.start()

        for thread in self.threadlist:
            thread.join()

    print "threads finished.. do something"

我做错了什么?


不知道你是否了解,仅仅从Thread继承并不足以使代码成为多线程的。例如,对于从threading.thread继承的线程,您需要将所需代码放在其“运行”方法中,然后在某个时刻调用start。出现在__init__方法中的代码仍将在主线程中运行。 - Kevin
谢谢回复。这个问题有点复杂。我已经根据你的评论更新了我的问题。首先,我在我的程序类函数中创建了一个线程列表。然后,我将这个列表发送给一个线程类,该类启动并加入线程,这样我就可以知道它们何时全部完成。当这些线程启动(myThread类)时,它们会再次调用程序类中的某个函数,在那里我想要更新我的GUI界面。 - Onedot618
也许我是从Thread()函数中错误地调用Program()函数?一开始我尝试通过简单的Program.some_function(arguments)来调用它们,但是一直收到有关未传递程序实例的错误。所以我不得不想出一个肮脏的(?)解决方法,声明"ex"为全局变量,并将其作为Program()实例参数传递。这样做的方式正确吗? - Onedot618
“官方”的方法是将参数添加到线程的run定义中,并像thread.start(args = (my_program_instance,))这样启动线程,以获取程序实例。但是全局变量也应该可以工作。总的来说,我认为你的spawnAndWaitThreadsThread看起来还不错。我认为下一个可能出现问题的地方是在myThread内部。 - Kevin
那么,如果我们浏览了数百个文件(使用os.path.listdir),按某些扩展名过滤它们,提取它们的时间戳,将结果串联成一个字符串,然后尝试在飞行中逐一将这些数百个字符串写入多个QListWidgets中,这不应该使程序冻结,对吧? - Onedot618
1个回答

0
我通过在主Program()类中添加一个非常简单的函数来解决这个问题,该函数只有一个任务-启动一个非常简单的线程,该线程反过来返回到Program()类并调用那里的一个函数,然后开始构造我的线程列表,将它们发送到另一个线程类等。
结果GUI不再冻结。

无法理解。如果您不能提供实际代码,请用示例进行说明。 - nish

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接