Pyqt5 中的 Qthreading

Qthreading In Pyqt5

我正在开发一款应用程序,您可以在其中输入亚马逊产品 URL 和 phone 编号,当该商品有货时它会向您发送短信。我正在使用 URL 并且我将拥有它,以便当您按下开始按钮时它会转到您输入的 URL 并检查它是否缺货但是当我按下开始时按钮程序崩溃并说没有响应。这是我的代码。

from PyQt5 import QtCore, QtGui, QtWidgets
import requests
from bs4 import BeautifulSoup
import time
import threading
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import *

class Ui_AmazonStockCheck(object):
        def setupUi(self, AmazonStockCheck):
            AmazonStockCheck.setObjectName("AmazonStockCheck")
            AmazonStockCheck.resize(804, 617)
            AmazonStockCheck.setWindowTitle('Stocker')
            self.centralwidget = QtWidgets.QWidget(AmazonStockCheck)
            self.centralwidget.setObjectName("centralwidget")
            self.urlBox = QtWidgets.QLineEdit(self.centralwidget)
            self.urlBox.setGeometry(QtCore.QRect(50, 90, 161, 51))
            self.urlBox.setObjectName("urlBox")
            font = QtGui.QFont()
            font.setFamily("Bebas Neue")
            font.setPointSize(20)
            self.phonenumberBox = QtWidgets.QLineEdit(self.centralwidget)
            self.phonenumberBox.setGeometry(QtCore.QRect(590, 90, 161, 51))
            self.phonenumberBox.setObjectName("phonenumberBox")
            self.producturl = QtWidgets.QLabel(self.centralwidget)
            self.producturl.setGeometry(QtCore.QRect(60, 50, 111, 31))
            font = QtGui.QFont()
            font.setFamily("Bebas Neue")
            font.setPointSize(20)
            self.producturl.setFont(font)
            self.producturl.setObjectName("producturl")
            self.phonenumber = QtWidgets.QLabel(self.centralwidget)
            self.phonenumber.setGeometry(QtCore.QRect(600, 60, 131, 31))
            font = QtGui.QFont()
            font.setFamily("Bebas Neue")
            font.setPointSize(20)
            self.phonenumber.setFont(font)
            self.phonenumber.setObjectName("phonenumber")
            self.startbutton = QtWidgets.QPushButton(self.centralwidget)
            self.startbutton.setGeometry(QtCore.QRect(320, 160, 141, 41))
            self.startbutton.clicked.connect(lambda: self.onclick(self.urlBox.text()))
            font = QtGui.QFont()
            font.setFamily("Bebas Neue")
            font.setPointSize(16)
            self.startbutton.setFont(font)
            self.startbutton.setObjectName("startbutton")
            self.abouttext = QtWidgets.QLabel(self.centralwidget)
            self.abouttext.setGeometry(QtCore.QRect(120, 240, 601, 231))
            font = QtGui.QFont()
            font.setFamily("Bebas Neue")
            font.setPointSize(28)
            self.abouttext.setFont(font)
            self.abouttext.setObjectName("abouttext")
            AmazonStockCheck.setCentralWidget(self.centralwidget)
            self.menubar = QtWidgets.QMenuBar(AmazonStockCheck)
            self.menubar.setGeometry(QtCore.QRect(0, 0, 804, 21))
            self.menubar.setObjectName("menubar")
            AmazonStockCheck.setMenuBar(self.menubar)
            self.statusbar = QtWidgets.QStatusBar(AmazonStockCheck)
            self.statusbar.setObjectName("statusbar")
            AmazonStockCheck.setStatusBar(self.statusbar)

            self.retranslateUi(AmazonStockCheck)
            QtCore.QMetaObject.connectSlotsByName(AmazonStockCheck)

        def retranslateUi(self, AmazonStockCheck):
                _translate = QtCore.QCoreApplication.translate
                AmazonStockCheck.setWindowTitle(_translate("AmazonStockCheck", "MainWindow"))
                self.producturl.setText(_translate("AmazonStockCheck", "Product Url"))
                self.phonenumber.setText(_translate("AmazonStockCheck", "Phone Number"))
                self.startbutton.setText(_translate("AmazonStockCheck", "Start"))
                self.abouttext.setText(_translate("AmazonStockCheck", "App developed by Gabriel Zebersky Beta Version"))


        def onclick(self, url):
            headers = {"User-Agent": 'Mozilla/5.0 (X11; Linux x86_64)', 'Cache-Control': 'no-cache', "Pragma": "no-cache"}
            page = requests.get(url, headers=headers)
            soup = BeautifulSoup(page.content, 'html.parser')
            while True:
                if soup.find(id='outOfStock'):
                    print('NOT IN STOCK')
                    time.sleep(2)
            else:
                print('IN STOCK')


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    AmazonStockCheck = QtWidgets.QMainWindow()
    ui = Ui_AmazonStockCheck()
    ui.setupUi(AmazonStockCheck)
    AmazonStockCheck.show()
    sys.exit(app.exec_())

我看到线程也许可以工作。有什么方法可以使用 Qthread 或 Qthreadpool 来解决这个问题吗?

Windows 会告诉您您的应用程序(假设)崩溃是因为它已挂起。这意味着代码没有处理任何事件(因此应用程序被冻结),可能是因为它被困在做其他事情。

在您的应用程序中,当您单击按钮时,将调用 onclick 方法。但是这个函数永远不会 returns 因为它会无限期地检查项目可用性(由于 while True 而没有 break)。
所做的是在单击按钮后主线程(在 PyQt 中处理 GUI)卡在这个方法中。

确实,您应该为此使用一个线程:辅助线程会陷入循环,但主线程仍然可以 运行 并处理事件。

这里是一个不阻塞主线程的 worker 的最小示例:

import random
import time
import typing

from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_AmazonStockCheck(object):
        def setupUi(self, AmazonStockCheck):
            AmazonStockCheck.resize(804, 617)
            AmazonStockCheck.setWindowTitle('Stocker')
            self.centralwidget = QtWidgets.QWidget(AmazonStockCheck)
            self.urlBox = QtWidgets.QLineEdit(self.centralwidget)
            self.urlBox.setGeometry(QtCore.QRect(50, 90, 161, 51))
            self.startbutton = QtWidgets.QPushButton("Start", self.centralwidget)
            self.startbutton.setGeometry(QtCore.QRect(320, 160, 141, 41))
            self.startbutton.clicked.connect(lambda: self.onclick(self.urlBox.text()))
            QtCore.QMetaObject.connectSlotsByName(AmazonStockCheck)

            layout = QtWidgets.QVBoxLayout()
            layout.addWidget(self.urlBox)
            layout.addWidget(self.startbutton)
            self.centralwidget.setLayout(layout)
            AmazonStockCheck.setCentralWidget(self.centralwidget)

        def onclick(self, url):
            print(f"starting to check availability for {url!r}")
            self.checker = AvailabilityChecker(url, self.centralwidget)
            print("checker has started")
            self.checker.availability_update.connect(
                lambda is_available: print(f"received update: available={is_available}")
            )


class AvailabilityChecker(QtCore.QObject):
    def __init__(self, url, parent: QtCore.QObject):
        super().__init__(None)  # a QObject with a parent can not be moveToThread'd
        self.url = url

        self._thread = QtCore.QThread(parent)
        self.moveToThread(self._thread)
        assert QtCore.QThread.currentThread() != self._thread  # we are still running in the caller's thread
        self._thread.started.connect(self.check_indefinitely)
        self._thread.start()

    def check_indefinitely(self) -> typing.NoReturn:
        assert QtCore.QThread.currentThread() == self._thread  # we are now running in our dedicated thread
        while True:
            is_available = (random.randint(0, 10) == 9)
            if is_available:
                print("AVAILABLE")
            else:
                print("NOT AVAILABLE")
            self.availability_update.emit(is_available)
            time.sleep(2)

    availability_update = QtCore.pyqtSignal([bool])


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    main_window = QtWidgets.QMainWindow()
    ui = Ui_AmazonStockCheck()
    ui.setupUi(main_window)
    main_window.show()
    sys.exit(app.exec_())

您可以看到您仍然可以使用 GUI,而 worker 在其自己的线程中不断检查可用性(为了方便,我只是 random)。

我只是将 lambda 连接到 worker 发出的信号,但您应该将它连接到 GUI 的方法以更新其状态(更改颜色、文本等)。

此外,线程 运行 无休止地运行,因此您的应用程序不会干净地退出。您应该考虑在您的 worker 上设置一个插槽来更改布尔值 should_stop,并将循环更改为 while not should_stop,以便您可以调用该插槽使您的线程在不久之后退出。

但我希望你能明白我回答的要点。是的,在线程中使用一个对象(“工人”)。使用信号在您的工作人员和 GUI 之间进行通信。有很多关于如何做到这一点的示例和文档。

如果适合你,我发现让 worker class 也继承 QRunnable(除了 signal/slots 的 QObject)可以将所有线程委托给 QThreadPool,这大大简化了代码。