QtWebPageRenders SIGILL 问题

QtWebPageRenderer SIGILL issue

我的问题总结在标题中。当我在 QtWebPageRenderer 的实例上调用方法 setHtml 时,发出 SIGILL 信号并且我的应用程序停止运行。

我知道这个问题是由错误的 Qt5 动态库引起的,但我安装了它:

sudo pip install PyQt5 --only-binary PyQt5
sudo pip install PyQtWebEngine --only-binary PyQtWebEngine

所以我想我会得到正确的预编译库。当我尝试在没有 --only-binary 的情况下安装 PyQt5 时,我总是以一些奇怪的编译错误结束。 qmake 之类的东西不在 PATH 中,即使它在 PATH 中,我也可以从 shell.

调用 qmake

所以我的问题是,如何在没有任何 SIGILL 的情况下在 Fedora 31 上制作 PyQt5 运行。

编辑:

以下代码可以重现该问题。关于 SIGILL 的信息有点不准确,因为第一个信号实际上是 SIGTRAP,在我在 gdb 中点击 continue 之后,我得到了 SIGILL。这暗示 Qt 实际上是在试图对我说些什么,尽管不是很直观。

玩了一会儿,发现不用线程也可以。这是否意味着 Qt 强制用户使用 QThread 而不是 python 线程?或者这意味着我不能在事件循环为 运行?

的线程外调用 Qt objects 的方法
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 的线程 class 那么你必须实现两个条件:

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()

实际上QThreadthreading.Thread()都是OS的native thread handlers,所以在实际中可以说是QThread是一个 threading.Thread() + QObject,在辅助线程上有一个事件循环 运行ning。


另一方面,如果您的 objective 是从它不属于的线程调用函数,那么您应该使用 中指出的异步方法。

在这种情况下最简单的就是使用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)

    <b>@QtCore.pyqtSlot(QtCore.QUrl)
    def load(self, url):
        QWebEnginePage.load(self, url)</b>


class Runner(threading.Thread):
    def __init__(self, web_view):
        self.web_view = web_view
        threading.Thread.__init__(self)
        self.daemon = True

    def run(self):
        <b>url = QtCore.QUrl("https://www.worldometers.info/coronavirus/")
        QtCore.QMetaObject.invokeMethod(
            self.web_view,
            "load",
            QtCore.Qt.QueuedConnection,
            QtCore.Q_ARG(QtCore.QUrl, url),
        )</b>


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

<b>from functools import partial</b>
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):
        <b>wrapper = partial(
            self.web_view.load,
            QtCore.QUrl("https://www.worldometers.info/coronavirus/"),
        )
        QtCore.QTimer.singleShot(0, wrapper)</b>


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()