我的问题总结在标题中。当我在setHtml
实例上调用方法QtWebPageRenderer
时,将发出SIGILL信号,并且我的应用程序关闭。
我知道此问题是由错误的Qt5动态库引起的,但我通过以下方式安装了它:
sudo pip install PyQt5 --only-binary PyQt5
sudo pip install PyQtWebEngine --only-binary PyQtWebEngine
所以我想我会得到正确的预编译库。当我尝试不使用--only-binary
安装PyQt5时,总是会遇到一些奇怪的编译错误。尽管qmake
不在PATH中,但我可以从shell调用qmake
。
所以我的问题是,如何使PyQt5在Fedora 31上运行而没有任何SIGILL。
编辑:
以下代码可以复制该问题。关于SIGILL的信息有点不准确,因为第一个信号实际上是SIGTRAP,在gdb中单击continue
之后,我得到了SIGILL。这暗示了Qt实际上是想对我说些什么,尽管不是很直观。
经过一番尝试之后,我发现没有线程,就可以了。这是否意味着Qt强制用户使用QThread而不是python线程?还是这意味着我无法在运行事件循环的线程之外调用Qt对象的方法?
import signal
import sys
import threading
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWebEngineWidgets import QWebEnginePage
class WebView(QWebEnginePage):
def __init__(self):
QWebEnginePage.__init__(self)
self.loadFinished.connect(self.on_load_finish)
def print_result(self, data):
print("-" * 30)
print(data)
with open("temp.html", "wb") as hndl:
hndl.write(data.encode("utf-8"))
def on_load_finish(self):
self.toHtml(self.print_result)
class Runner(threading.Thread):
def __init__(self, web_view):
self.web_view = web_view
threading.Thread.__init__(self)
self.daemon = True
def run(self):
self.web_view.load(QtCore.QUrl("https://www.worldometers.info/coronavirus/"))
def main():
signal.signal(signal.SIGINT, signal.SIG_DFL)
app = QtWidgets.QApplication(sys.argv)
web_view = WebView()
runner = Runner(web_view)
runner.start()
app.exec_()
if __name__ == "__main__":
main()
您必须有几个限制:
QObject不是thread-safe,因此在主线程中创建“ web_view”时,在辅助线程中对其进行修改是不安全的
由于QWebEnginePage任务异步运行,因此您需要Qt事件循环。
因此,如果要使用python的Thread类,则必须同时实现这两个条件:
import signal
import sys
import threading
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWebEngineWidgets import QWebEnginePage
class WebView(QWebEnginePage):
def __init__(self):
QWebEnginePage.__init__(self)
self.loadFinished.connect(self.on_load_finish)
def print_result(self, data):
print("-" * 30)
print(data)
with open("temp.html", "wb") as hndl:
hndl.write(data.encode("utf-8"))
def on_load_finish(self):
self.toHtml(self.print_result)
class Runner(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.daemon = True
def run(self):
# The QWebEnginePage was created in a new thread and
# that thread has an eventloop
loop = QtCore.QEventLoop()
web_view = WebView()
web_view.load(QtCore.QUrl("https://www.worldometers.info/coronavirus/"))
loop.exec_()
def main():
signal.signal(signal.SIGINT, signal.SIG_DFL)
app = QtWidgets.QApplication(sys.argv)
runner = Runner()
runner.start()
app.exec_()
if __name__ == "__main__":
main()
实际上QThread
和threading.Thread()
是OS的本机线程handlers,因此实际上,可以说QThread是threading.Thread()
+ QObject
,其中有一个事件循环运行在辅助线程上。
另一方面,如果您的目标是从不属于该线程的线程中调用函数,则应使用this answer中指出的异步方法。
在这种情况下,最简单的方法是使用pyqtSlot + QMetaObject:
import signal
import sys
import threading
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWebEngineWidgets import QWebEnginePage
class WebView(QWebEnginePage):
def __init__(self):
QWebEnginePage.__init__(self)
self.loadFinished.connect(self.on_load_finish)
def print_result(self, data):
print("-" * 30)
print(data)
with open("temp.html", "wb") as hndl:
hndl.write(data.encode("utf-8"))
def on_load_finish(self):
self.toHtml(self.print_result)
@QtCore.pyqtSlot(QtCore.QUrl)
def load(self, url):
QWebEnginePage.load(self, url)
class Runner(threading.Thread):
def __init__(self, web_view):
self.web_view = web_view
threading.Thread.__init__(self)
self.daemon = True
def run(self):
url = QtCore.QUrl("https://www.worldometers.info/coronavirus/")
QtCore.QMetaObject.invokeMethod(
self.web_view,
"load",
QtCore.Qt.QueuedConnection,
QtCore.Q_ARG(QtCore.QUrl, url),
)
def main():
signal.signal(signal.SIGINT, signal.SIG_DFL)
app = QtWidgets.QApplication(sys.argv)
web_view = WebView()
runner = Runner(web_view)
runner.start()
app.exec_()
if __name__ == "__main__":
main()
或functools.partial()+ QTimer
from functools import partial
import signal
import sys
import threading
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWebEngineWidgets import QWebEnginePage
class WebView(QWebEnginePage):
def __init__(self):
QWebEnginePage.__init__(self)
self.loadFinished.connect(self.on_load_finish)
def print_result(self, data):
print("-" * 30)
print(data)
with open("temp.html", "wb") as hndl:
hndl.write(data.encode("utf-8"))
def on_load_finish(self):
self.toHtml(self.print_result)
class Runner(threading.Thread):
def __init__(self, web_view):
self.web_view = web_view
threading.Thread.__init__(self)
self.daemon = True
def run(self):
wrapper = partial(
self.web_view.load,
QtCore.QUrl("https://www.worldometers.info/coronavirus/"),
)
QtCore.QTimer.singleShot(0, wrapper)
def main():
signal.signal(signal.SIGINT, signal.SIG_DFL)
app = QtWidgets.QApplication(sys.argv)
web_view = WebView()
runner = Runner(web_view)
runner.start()
app.exec_()
if __name__ == "__main__":
main()