简体   繁体   English

在调用函数时如何防止GUI冻结? (PyQT4,Python3)

[英]How do I keep GUI from freezing, while calling a function? (PyQT4, Python3)

Question Background: 问题背景:

I'm new to PyQT4. 我是PyQT4的新手。 I am developing a program with it and I am web scraping to get data into my program. 我正在开发一个程序,我正在网上抓取数据到我的程序。 While the information is downloading my GUI locks up. 在信息下载时,我的GUI锁定。 I would like to call this function in a separate background thread, perhaps using QThread, but I am having a tough time wrapping my head around QThread, Qt in general, and the slot/signal way of communication. 我想在一个单独的后台线程中调用这个函数,也许使用QThread,但是我很难绕过QThread,Qt,以及插槽/信号通信方式。

I have read about making a generic worker thread that will call any function passed to it. 我已经读过关于创建一个泛型工作线程,它将调用传递给它的任何函数。 I don't know how to implement it in my main file so that I can run my functions as a background process. 我不知道如何在我的主文件中实现它,以便我可以将我的函数作为后台进程运行。 If any example code could be shown, please explain each line in detail as I am not understanding the process. 如果可以显示任何示例代码,请详细解释每一行,因为我不了解该过程。

Questions: 问题:

  1. How can I prevent my GUI from freezing while a function is running? 如何在函数运行时阻止GUI冻结?
  2. How could I use a background thread to run functions from my class? 我如何使用后台线程来运行我的类中的函数?

Code: 码:

My ui is loaded in from an external file created by Qt 4 Designer. 我的ui是从Qt 4 Designer创建的外部文件中加载的。

Full files on Github Github上的完整文件

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName(_fromUtf8("MainWindow"))

main.py (Main file) main.py(主文件)

def connections():
    # If button is clicked, call summary(), which web scrapes 
    # for data. This could take 5-30 seconds, this freezes UI.
    ui.btnRefreshSummary.clicked.connect(lambda: summary())

# Refresh items in gui
def refresh_ui():
    if summary_data != []:
        ui.valWatching.setText(summary_data[0])
        ui.valBidding.setText(summary_data[1])
        ui.valWon.setText(summary_data[2])
        ui.valNotWon.setText(summary_data[3])
        ui.valPurchases.setText(summary_data[4])
        ui.valInvoices.setText(summary_data[5])

def login():
    # Scrape website and login while in background; 
    # This locks up GUI until it completes.
    # Pretend this sleep command is the time it takes to login
    time.sleep(5)  # <-This would lock it up for 5 seconds

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    MainWindow = QtGui.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    connections()
    # Load credentials from file.
    with open('login.txt') as f:
        credentials = f.readline().strip().split(':')
    f.closed

    # Login, download summary, then refresh the UI.
    b = Biddergy()
    b.login(credentials[0],credentials[1])
    summary_data = b.summary()
    b.logout()

    refresh_ui()
    sys.exit(app.exec_())

It's not clear from the example code why connections() would block (given the code it contains), or why login() shouldn't block (given that that is what login dialogs normally do). 从示例代码中不清楚为什么connections() 阻塞(给定它包含的代码),或者为什么login() 不应该阻塞(假设登录对话框通常这样做)。 But anyway, the worker class in your example can be converted to a QThread like this: 但无论如何,你的例子中的worker类可以像这样转换为QThread

class Worker(QThread):
    intReady = pyqtSignal(int)

    def run(self):
        for i in range(1, 10):
            time.sleep(1)
            self.intReady.emit(i)

and then it can be used like this: 然后它可以像这样使用:

    # connections()
    # login()
    def slot(arg='finished'): print(arg)
    thread = Worker()
    thread.intReady.connect(slot)
    thread.finished.connect(slot)
    thread.start()

There are many other ways of achieving the same thing - but which one is most appropriate, and how it would actually be implemented, depends on the details of how your application is intended to work. 还有许多其他方法可以实现相同的功能 - 但哪一种最合适,以及实际如何实现,取决于应用程序的工作方式的详细信息。

With this updated code, I am finally able to start my functions in the background. 有了这个更新的代码,我终于可以在后台启动我的功能。 Now off to learning how to make my background thread communicate with the main UI thread. 现在开始学习如何使我的后台线程与主UI线程进行通信。 Much thanks to @ekhumoro for being extremely patient with me. 非常感谢@ekhumoro对我非常耐心。

#!/usr/bin/env python3
from PySide import QtGui, QtCore
from PySide.QtCore import QThread, QObject, Signal, Slot
from main_gui import Ui_MainWindow  # my UI from Qt4 Designer(pyside-uic)
from Scrapers import Biddergy       # My own class
import sys
import queue

class BiddergyWrapper(QThread):
    def __init__(self, q, loop_time=1.0/60):
        self.q = q
        self.timeout = loop_time
        super(BiddergyWrapper, self).__init__()

    def onThread(self, function, *args, **kwargs):
        self.q.put((function, args, kwargs))

    def run(self):
        while True:
            try:
                function, args, kwargs = self.q.get(timeout=self.timeout)
                function(*args, **kwargs)
            except queue.Empty:
                self.idle()

    def idle(self):
        pass

    def _summary(self):
        b.summary()

    def summary(self):
        self.onThread(self._summary)

    def _login(self):
        b.login()

    def login(self):
        self.onThread(self._login())

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    MainWindow = QtGui.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    ui.btnRefreshSummary.clicked.connect(lambda: bw.summary())

    # Load credentials from file.
    with open('login.txt') as f:
        credentials = f.readline().strip().split(':')

    # Login, download summary, then refresh the UI.
    b = Biddergy(credentials[0], credentials[1])
    request_queue = queue.Queue()
    bw = BiddergyWrapper(request_queue)
    bw.start()

    # Run QApplication
    app.exec_()
    # Begin "Graceful stop?"
    bw.quit()
    b.logout()
    sys.exit()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM