QtWebPageRenderer SIGILL issue

梦想与她 提交于 2020-06-27 16:25:10

问题


My problem is summed in title. When I call method setHtml on instance of QtWebPageRenderer, SIGILL signal is emitted and my application goes down.

I'm aware that this issue is caused by bad Qt5 dynamic library but I installed it with:

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

so I thought I will get correct precompiled library. When I tried to install PyQt5 without --only-binary, I always ended with some strange compilation error. Something like qmake is not in PATH even though it is and I'm able to call qmake from shell.

So my question is, how to make PyQt5 running on Fedora 31 without any SIGILLs.

EDIT:

Following code can replicate the issue. That information about SIGILL is little inaccurate because first signal is actually SIGTRAP, after I hit continue in gdb, I got SIGILL. This hints that Qt is actually trying to say something to me, although in not very intuitive way.

After some playing around with it, I found that without thread, its ok. Does this mean that Qt forces user to use QThread and not python threads? Or it means that I can't call methods of Qt objects outside of thread where event loop is running?

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

回答1:


You have to have several restrictions:

  • A QObject is not thread-safe so when creating "web_view" in the main thread then it is not safe to modify it in the secondary thread

  • Since the QWebEnginePage tasks run asynchronously then you need a Qt eventloop.

So if you want to use python's Thread class then you must implement both conditions:

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

In reality QThread and threading.Thread() are native thread handlers of the OS, so in practical terms it can be said that QThread is a threading.Thread() + QObject with an eventloop running on the secondary thread.


On the other hand, if your objective is to call a function from a thread to which it does not belong, then you should use asynchronous methods as pointed out in this answer.

In this case the simplest is to use 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()

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


来源:https://stackoverflow.com/questions/62330952/qtwebpagerenderer-sigill-issue

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!