相关文章推荐
Your browser does not seem to support JavaScript. As a result, your viewing experience will be diminished, and you have been placed in read-only mode . Please download a browser that supports JavaScript, or enable it if it's disabled (i.e. NoScript).

I'm not experienced in Qt, I've been using PySide6 to create a small interface which asks for data from the user.

Currently, I'm getting a Segfault with the following code (simplified from my actual use case)

from PySide6.QtCore import *
from PySide6.QtWidgets import *
class Worker(QThread):
    finished = Signal()
    # progressed = Signal(int)
    def run(self):
        class Dialog(QDialog):
            def __init__(self):
                super().__init__()
                QVBoxLayout(self).addWidget(QPushButton(self))
        print("Processing some data ...")
        print("Found incomplete data. Asking user what to do")
        Dialog().exec()
class Main(QWidget):
    def __init__(self):
        super().__init__()
        print("Starting main application")
        self.worker= None
        self.vbl = QVBoxLayout(self)
        self.pb = QPushButton(self)
        self.pb.clicked.connect(self.onclick)
        self.vbl.addWidget(self.pb)
    def onclick(self):
        if self.worker != None:
            print("Let the previous process finish")
            return
        print("Start process on a separate thread to not block the UI")
        self.worker = Worker()
        self.worker.finished.connect(self.onfinish)
        self.worker.start()
    def onfinish(self):
        self.worker = None
def main(args: list[str]):
    app = QApplication(args)
    window = Main()
    window.show()
    app.exec()
if __name__ == "__main__":
    from sys import argv
    main(argv)

With the following code, if I repeatedly click the button, the application segfaults, sometimes complaining about calling endPaint with an active painter. Sometimes it just segfaults directly.

 ayhon@slab 2023-09-04 $ python test.py
Starting main application
Requested decoration  "gnome"  not found, falling back to default
Start process on a separate thread to not block the UI
Processing some data ...
Found incomplete data. Asking user what to do
Requested decoration  "gnome"  not found, falling back to default
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::setCompositionMode: Painter not active
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::setCompositionMode: Painter not active
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::setCompositionMode: Painter not active
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::setCompositionMode: Painter not active
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::setCompositionMode: Painter not active
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::setCompositionMode: Painter not active
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::setCompositionMode: Painter not active
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::setCompositionMode: Painter not active
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::setCompositionMode: Painter not active
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::setCompositionMode: Painter not active
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::setCompositionMode: Painter not active
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::setCompositionMode: Painter not active
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::setCompositionMode: Painter not active
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::setCompositionMode: Painter not active
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::setCompositionMode: Painter not active
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::setCompositionMode: Painter not active
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::setCompositionMode: Painter not active
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::setCompositionMode: Painter not active
QPainter::setCompositionMode: Painter not active
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
Segmentation fault (core dumped)

I'm using Fedora 38 on GNOME. In my actual usecase, the application crashes as soon as I hit the Ok or Cancel button in a dialog

@JonB Thank you, I suspected something of the sort. However, I felt that my use case was reasonable. How could I re-architecture my code to accomplish something similar?

I use a QThread since I need to do some work that blocks the UI thread, downloading data from a database. However, I need user input since that data may be incomplete, in which case I'd like to query the user on how to proceed. How would it be advised to do this without spawning a Dialog from a different thread? Do I need to communicate with the UI thread with signals to ask it to clean my data?

Sorry if this is a bit off-topic

@ayhon said in Segment fault when opening Dialog from a different QThread:

Do I need to communicate with the UI thread with signals to ask it to clean my data?

Exactly that. You can and should send signals and connect slots across threads. Only main UI thread to do any UI, both output and input.

Qt's own (SQL) database classes exchange data with a variety of SQL backends and you do not need to use it from threads, usually.

If I were learning Qt I would start without threads and only add them when I found I really needed them. And even then learn that you'll get things wrong!

@JonB I see, thank you for your time and the confirmation!

Then, to achieve my use case I'd have to do something like:

This seems to me like a bit of "callback hell". Is there any way to make this a bit more developer-friendly? Perhaps an async/await syntax?

This is purely out of curiosity, for my use case this solution works

This is really annoyingly tricky to do it right. If QtConcurrent works for you, then you should use it.

Here is what I have learned so far for exactly your problem (calling GUI functions from another thread) when other approaches don't work well. (Though I only know C++ and not Python, but the ideas would be similar.)

In order to call any GUI function from another thread we can use QMetaObject::invokeMethod(...). In C++ the first parameter is a context object where we can plug in qApp (pointer to the only instance of QApplication). As a second parameter I would put in a lambda which has the code that should be executed inside the GUI thread.

However, this is not a blocking call, yet. So, we cannot wait in this way for the answer from the dialog. For this I am using a QMutex to synchronize the two threads. Here are the steps:

  • Create a QMutex object inside the calling thread and lock it.
  • Call the GUI code (a lambda in C++) with the method described above. As last statement in the GUI code unlock the mutex.
  • Inside the calling thread call lock on the mutex again. This will only work once it is unlocked by the GUI thread. This means we'll wait until the GUI code has run.
  • For clean up unlock the mutex.
  • I hope this translates well into Python. But, as mentioned before, got with QtConcurrent if this works for you.

  • Create a QMutex object inside the calling thread and lock it.
  • Call the GUI code (a lambda in C++) with the method described above. As last statement in the GUI code unlock the mutex.
  • Inside the calling thread call lock on the mutex again. This will only work once it is unlocked by the GUI thread. This means we'll wait until the GUI code has run.
  • For clean up unlock the mutex.
  • This looks like a reinvention of Qt::BlockingQueuedConnection

    @jeremy_k said in Segment fault when opening Dialog from a different QThread:

    This looks like a reinvention of Qt::BlockingQueuedConnection

    Maybe it is. However, I don't like to write a signal and a slot for just one line of code. Instead I use lambdas in these places. That's why I use QMetaObject::invokeMethod instead of a signal/slot connection. As far as I know there is no equivalent of Qt::BlockingQueuedConnection for invokeMethod. Hence the workaround/reimplementation.

    It is best to put this all into a library to make it easier to not get wrong. I have done so: https://github.com/SimonSchroeder/QtThreadHelper . This is wrapped into guiThread([](){ ... }) and guiThread(WorkerThread::SYNC, [](){ ... }). The latter will block and wait.

    @SimonSchroeder said in Segment fault when opening Dialog from a different QThread:

    As far as I know there is no equivalent of Qt::BlockingQueuedConnection for invokeMethod

    Qt::ConnectionType is the third argument of ~ half of the overloads of QMetaObject::invokeMethod, going back to at least Qt 4.5.1.

    @jeremy_k said in Segment fault when opening Dialog from a different QThread:

    Qt::ConnectionType is the third argument of ~ half of the overloads of QMetaObject::invokeMethod, going back to at least Qt 4.5.1.

    I didn't know that! Looks like I should change the implementation then...

     
    推荐文章