Can QWidget::update() be called in a sub thread other than the GUI thread?



  • As the title's question, I wrote a program to render the complex graphics at a sub-thread (want not to occupy much cpu time for the GUI thread in order to speed up user interaction response) into a QImage, after that in this sub-thread render call stack, call update() directly to let QT deliver a queued connection (I guess it act as this) which lead to a QWidget::paintEvent in GUI thread to drawImage to the widget.

    This do works, but I found after running a while, occasionally, the GUI thread did not response to the update() call by the sub-thread (means i called update(), but no paintEvent received), but only response to the system level update/paintEvent cycle (for example, get/lose focus of the main window).

    I took a glance at the QT code, seems the system's updatePending member var is true that causes the QT ignores my update call. But i am not sure.

    Can any friend tell me whether the update() is designed to be able to called in sub-thread other than GUI?


  • Moderators

    No. GUI classes are designed to be manipulated from the main thread only (update() counts as manipulation). That's why the main thread is also called the "GUI thread". From http://qt-project.org/doc/qt-5.0/qtcore/threads-qobject.html :
    [quote]...the GUI classes, notably QWidget and all its subclasses, are not reentrant. They can only be used from the main thread.[/quote]


  • Moderators

    according to a section in the link you've posted:
    http://qt-project.org/doc/qt-5.0/qtcore/threads-qobject.html#signals-and-slots-across-threads
    [quote]
    Queued Connection The slot is invoked when control returns to the event loop of the receiver's thread. The slot is executed in the receiver's thread.
    [/quote]
    and update() is a slot.
    The QWidget must live in the GUI thread of course.



  • [quote author="raven-worx" date="1371651664"]according to a section in the link you've posted:
    http://qt-project.org/doc/qt-5.0/qtcore/threads-qobject.html#signals-and-slots-across-threads
    [quote]
    Queued Connection The slot is invoked when control returns to the event loop of the receiver's thread. The slot is executed in the receiver's thread.
    [/quote]
    and update() is a slot.
    The QWidget must live in the GUI thread of course.[/quote]

    Yes, QWidget must live in the GUI thread.
    My program just do the following in the sub-thread:
    {
    QImage img = QImage(...);
    QPainter painter(&img);
    painter->drawPath(...);
    update();
    }

    update() itself is a queued connecton to the GUI thread(am i right?). And GUI thread's slot (simply treat it to be) paintEvent is called. Which performs the following:

    {
    QPainter painter (this); // this must be in GUI thread otherwise exception of QT thrown.

    painter.drawImge(img);
    }

    So, my program never operate on the QWidget based QPainter in a sub-thread. What is in the sub-thread is the access to the data which defined by my QWidget derived class, and those data are all of my GUI - independent business logic data.

    So actually, I want to ask is:
    What does "QWidget and all its subclasses, are not reentrant, They can only be used from the main thread." really mean?
    Does it mean, the QPainter of the QWidget (especially the paintEvent), or the whole QWidget class (even the user added member vars in it's derived class).



  • [quote author="JKSH" date="1371651305"]No. GUI classes are designed to be manipulated from the main thread only (update() counts as manipulation). That's why the main thread is also called the "GUI thread". From http://qt-project.org/doc/qt-5.0/qtcore/threads-qobject.html :
    [quote]...the GUI classes, notably QWidget and all its subclasses, are not reentrant. They can only be used from the main thread.[/quote][/quote]

    Hi, JKSH, there is still a official statement in the link you provided:
    "In practice, the impossibility of using GUI classes in other threads than the main thread can easily be worked around by putting time-consuming operations in a separate worker thread and displaying the results on screen in the main thread when the worker thread is finished. This is the approach used for implementing the Mandelbrot and the Blocking Fortune Client example."

    So I think the way of painting a QImage in sub-thread, and do a BitBlt like copy to the Widget in GUI thread is an way.
    The key is, could I call update() directly in sub-thread, or have to write a signal/slot on my class to queue the update() call to the GUI thread?


  • Moderators

    [quote author="rockvegas" date="1371652332"]My program just do the following in the sub-thread:
    {
    QImage img = QImage(...);
    QPainter painter(&img);
    painter->drawPath(...);
    update();
    }
    update() itself is a queued connecton to the GUI thread(am i right?).[/quote]Hi rockvegas,

    That code has no queued connections; it is making direct function calls. If that code is run in a sub-thread, then your program will run the QPainter constructor, drawPath(), and update() in that sub-thread (which are not allowed).

    If you want to use a queued connection, you must first connect the slot to a signal:
    @
    QObject::connect(this, SIGNAL(mySignal()), receiver, SLOT(mySlot()), Qt::QueuedConnection);
    @
    Then, emit the signal when you want to run the slot:
    @
    emit mySignal();
    // This will put receiver->mySlot() in the queue of the receiver's thread
    @

    [quote]So, my program never operate on the QWidget based QPainter in a sub-thread.[/quote]Actually, as I explained above, your code DOES operate on the QWidget and the QPainter in the sub-thread. You'll have to change it.

    [quote]So actually, I want to ask is:
    What does “QWidget and all its subclasses, are not reentrant, They can only be used from the main thread.” really mean?
    Does it mean, the QPainter of the QWidget (especially the paintEvent), or the whole QWidget class (even the user added member vars in it’s derived class).[/quote]

    • Thread-safe class: You can create instances of this class in different threads. Each instance can be accessed from multiple threads.
    • Reentrant class (not thread-safe): You can create instances of this class in different threads. However, each instance can only be accessed from one thread.
    • Non-reentrant class (also not thread-safe): You can only create instances of this class in one thread. Each instance can only be accessed from that thread.

    Specifically, QWidget is non-reentrant. You can only create QWidget objects in the main thread, and ALL of its functions must only be called from the main thread.

    (Well, if we want to be really picky, we can argue that QWidget::isVisible() and some user-added member functions can be called from another thread without any bad effects... but good code should not do that. Business logic data should be kept separate from the widgets.)

    For more details, see http://qt-project.org/doc/qt-5.0/qtcore/threads-reentrancy.html

    [quote]So I think the way of painting a QImage in sub-thread, and do a BitBlt like copy to the Widget in GUI thread is an way.[/quote]
    Yes, generating the QImage in a sub-thread is a good approach.

    [quote]The key is, could I call update() directly in sub-thread, or have to write a signal/slot on my class to queue the update() call to the GUI thread?[/quote]...However, you cannot call update() directly from the sub-thread. QWidget's functions should only be called from the main thread.

    After you finish creating the QImage in your sub-thread, you need to transfer it into your QWidget using a queued signal-slot connection. The slot should run in the GUI thread -- it should take the QImage, apply it, and call update()

    Something like this, for example:
    @
    //-------------
    // Class declarations/definitions
    //-------------
    class MyThreadedImageMaker : public QObject {
    ...

    void createImage() {
        QImage img;
        ...
        emit finished(img)
    }
    

    signals:
    void finished(const QImage &);
    };

    class MyWidget : public QWidget {
    ...

    public slots:
    void applyImage(const QImage & img) {
    // Draw the image to screen
    ...
    update();
    }
    }

    //-------------
    // Assuming that the objects have already been created in the correct threads,
    //-------------
    connect(myThreadedImageMaker, SIGNAL(finished(QImage)),
    myWidget, SLOT(applyImage(QImage)), Qt::QueuedConnection);
    }
    @



  • JKSH I have a little follow-up question connected to "this discussion":http://qt-project.org/forums/viewthread/29018/#129996.

    Would this be legal?
    @
    connect(myThreadedImageMaker, SIGNAL(finished()),
    myWidget, SLOT(update()), Qt::QueuedConnection);
    @

    I think its not (because the event triggering the GUI manipulating call, although its a QueuedConnection, comes from outside the main thread) but I'm really not sure.


  • Moderators

    [quote author="KA51O" date="1371718030"]
    Would this be legal?
    @
    connect(myThreadedImageMaker, SIGNAL(finished()),
    myWidget, SLOT(update()), Qt::QueuedConnection);
    @

    I think its not (because the event triggering the GUI manipulating call, although its a QueuedConnection, comes from outside the main thread) but I'm really not sure.[/quote]
    see the third post in this thread...



  • My problem is the section in the documentation mentioned by you is just commenting on the generall fact that - of course - the slot is executed in the context of the receivers thread. But this does not (at least for me) adress the problem that GUI objects like e.g. QWidget are not allowed to be modified directly from another thread than the main thread.


  • Moderators

    [quote author="KA51O" date="1371719288"]But this is does not (at least for me) adress the problem that GUI objects like e.g. QWidget are not allowed to be modified directly from another thread than the main thread.[/quote]
    Thats the point of queued connections: they do no direct manipulation! Basically an event is generated and sent to the object and the object executes the desired method "on itself". Thus there is no conflict between those 2 threads and also no relationship between the sender and receiver object.



  • Ok ok, I give up. Maybe I'm just overthinking this. To give my mind some peace I'll just build a small test app later.


  • Moderators

    Hi KA51O,

    I think raven-worx is right. I think you've mixed up "signal emission" with "widget manipulation". In simple terms, "Manipulate" == "Modify member variables".

    Let's follow the logic of a simple case:

    A queued signal is emitted from a worker thread.

    The slot -- QWidget::update() -- executes in the context of the GUI thread

    The slot contains code that modifies the QWidget's member variables. Question: Which thread modifies those member variables? Answer: The GUI thread (because the slot is running in the GUI thread)

    Conclusion: Even though the signal was emitted from the worker thread, the QWidget is manipulated by the GUI thread.

    To look at it from a different angle, here's why widget manipulation from a worker thread causes problems:

    • The OS's graphics system runs in the GUI thread, and reads the QWidget's member variables from the GUI thread.
    • If member variables are modified by a worker thread, they could be modified while the graphics system is in the middle of reading those variables
    • Therefore, a QWidget's member variables must not be modified from a worker thread; a QWidget must not be manipulated from a worker thread.

    The description above is oversimplified, but I hope that makes it understandable for you? :)



  • Thank you for the explanation. I guess I somehow made up my own rule when I first experimented with threading in Qt a few years ago and this old one got stuck, because after that I never really used threading for GUI stuff again X P



  • Hi, JKSH & raven-worx:

    Thanks very much for your detailed description.

    Yes, I looked into the QWidget::update() code, it do manipulate the backingstore in it's call stack directly, also marks the dirty area.

    So you are right that update() should only be called in GUI thread.

    And one more information is, update() triggers the widget's paintEvent by two kinds of way, one is sendEvent(QPaintEvent), another is postEvent(QPaintEvent), after it marks the dirty area, whether send or post, seems to be decided by QT, by default, the update() triggers a cross-thread postEvent to paintEvent().

    BTW, I still think, QT only explicit throw exception when create a QPainter out of paintEvent(), but does not explicit throw exception if we un-safely access QWidget's member in other thread (but exactly, we can not gurantee the behavior of this because QWidget's menbers are none-reentrance.
    However, if we derive a QWidget class, called MyWidget, and define a new member called m_var. I think, we still can access MyWidget::m_var via multiple thread, if we can safely lock on the access to m_var. QT itself really does nothing to explicitly restrict our accessing to the derived widget's access. Right?


  • Moderators

    You're both welcome :)

    [quote author="rockvegas" date="1371820708"]BTW, I still think, QT only explicit throw exception when create a QPainter out of paintEvent(), but does not explicit throw exception if we un-safely access QWidget's member in other thread (but exactly, we can not gurantee the behavior of this because QWidget's menbers are none-reentrance.
    However, if we derive a QWidget class, called MyWidget, and define a new member called m_var. I think, we still can access MyWidget::m_var via multiple thread, if we can safely lock on the access to m_var. QT itself really does nothing to explicitly restrict our accessing to the derived widget's access. Right?[/quote]Qt has been programmed to warn you if you create a QPainter illegally, but it has not been programmed to warn you if your custom variables and functions break the rules.

    So in theory, yes you can put a variable in a QWidget subclass that is modified by another thread. However, I don't recommend it -- it's inconsistent with the design of the QWidget class, and you'll risk confusing other programmers (and even yourself) in the future.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.