7

Under Qt 4.7.1, Qt Creator 2.1.0, OS X 10.6.8:

I have a QLabel in the mainwindow ui, which uses Courier New / 13, with room for four lines of text.

I create four lines of text, considerably shorter than the label is horizontally, of the general format:

"my text\r\n"

I filter the text before sending it along. The only characters in the cstring will be 0x0D, 0x0A, 0x20 (space) and from there up to lower case z (0x7A') and of course the terminating zero. No other control characters - if they are received from the source, I replace them with '*'

I send the four lines of text to the QLabel as a single zero-terminated cstring via setText()

I sometimes do this at a fairly high rate, several times a second at least -- this is RDBS data from an FM station so it changes in real time:

qDebug() << rbl;                    // data keeps coming to console
ui->fourLineLabel->setText(rbl);    // add this, display soon stops updating

This works. For a while. Then the display stops updating. This is the area at issue:

4-line QLabel
(source: fyngyrz.com)

If I leave everything else in, but take out the setText(), the problem does not occur.

I know that for some things, Qt wants painting to be done within a paint event. Is this also true of a setText() ?

Reading the docs on qt widgets, it says that widgets do their own painting within their own paint event... but the behavior here is very similar to the kind of malfappery that goes on when one actually tries to use a painter outside of a paint event. And it's definitely related to that setText(), so... mumble.

As I write this, the application has been running for hours without any display lockup, outputting the same text to the console via qDebug(). It takes about 5 minutes for the problem to occur if I uncomment the setText(). It's 100% repeatable.

Is there something I should be doing that I'm not doing, paint-wise or similar?

Thanks for any assistance.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
fyngyrz
  • 2,458
  • 2
  • 36
  • 43
  • yes, the ui thread is not painting your data properly. Use event listener and you should be fine. It happened to me several times. – Sreekar Dec 28 '15 at 00:38
  • Sreekar, I don't know what you mean by "use event listener" - should I provide a paint event for the window, even though I'm not painting? Is that it? – fyngyrz Dec 28 '15 at 03:15
  • "I know that for some things, Qt wants painting to be done within a paint event". Not just for some things, for all. But no normal method (other than exceptions like grabToImage) do any painting, but trigger repaints in the event loop when necessary, via QWidget::update(). So does QLabel::setText(). – Frank Osterfeld Dec 28 '15 at 08:13
  • "If I leave everything else in, but take out the setText(), the problem does not occur." - What does this mean? If you take out setText(), there's no updating at all. I assume you verified that the line with setText() keeps being called when you expect the updates to happen? – Frank Osterfeld Dec 28 '15 at 08:23
  • Frank, there's all kinds of updating going on. Just not to that widget. All the underlying code that generates the text is still running, as is everything else. It's a complex app. The problem occurs when I add that one method call. Yes, I verified that. I put a `qDebug()` right next to it, and it merrily keeps dumping the same data the setText() is supposed to be putting on-screen. But isn't. – fyngyrz Dec 28 '15 at 14:57
  • 1
    @fyngyrz do you call `ui->fourLineLabel->setText(rbl);` from non ui thread? – Shf Dec 28 '15 at 17:06
  • Yes, I call the routine that this is in from a non-ui thread. – fyngyrz Dec 28 '15 at 17:09
  • @Shf attention - I put out a bounty on a question, but it was YOUR comment here that led me to the answer. Go mention non-ui thread gfx access at the following question, and I will award you the bounty: be quick! http://stackoverflow.com/questions/26684409/qt-4-7-tcp-thread-data-transfer-causes-memory-leak – fyngyrz Jan 06 '16 at 03:51
  • @Shf -- okay, bounty ended, sorry. Here's what I will do, though: You get back to me at fyngyrz at gmail daht com, and I will set up all of my remaining reputation on this question as a bounty, you get in here and answer it, and I'll award your answer -- because believe me, it was you who led me to the solution for the problem here **and** the question for which I offered the bounty. I have to say, great insight, too. --Ben / fyngyrz – fyngyrz Jan 06 '16 at 04:28

2 Answers2

8

In general you should not update Qt controls from non UI thread, only a small amount of things is allowed to do regarding a painting in non UI thread - http://doc.qt.io/qt-4.8/threads-modules.html

If you need to update UI from non UI thread - use signals and slots (QueuedConnection or BlockingQueuedConnection connections, though make sure to not create deadlock with BlockingQueuedConnection). Or if you don't want to create additional signals and slots for some easy update - use invokeMethod (it can even return value and if you use it with BlockingQueuedConnection connection type, your thread will wait until UI is updated).

And a general advice - if you have a possibility - make one call for bulk of updates to UI instead of few small calls.

Shf
  • 3,463
  • 2
  • 26
  • 42
0

It is always advised that the GUI thread interfaces with all other objects through the signal-slot mechanism. In fact, no direct calls from and to the main thread are to be made. In that manner the GUI will be responsive, and we don't end up waiting for it to come back. Certainly polling solutions are not ideal, and should be avoided as they end up using cup resources without reason.

If one is using only QThread type threads then updating the GUI should be done by using the signal-slot mechanism. When events of data presented need to be serialized using the Qt::QueuedConnection is sufficient. In your case that is true.

If not using that , then signals may not be processed in the sequence emitted. Qt::BlockingQueuedConnection should be used only when we want to restrict the caller continue from processing before the slot on the receiver has completed. This is very rarely the case for processing that happens on the GUI thread.

Special care has to be taken when we want to connect from a non-qt thread, e.g. An std thread, because the objects created e.g. in a native thread will not be known on the receiver end.

One way to update the ui from a non-ui thread is to serialize and copy your messages. Do the following (works even for non-QThreads e.g. boost::thread ):

  • Setup a singleton QObject that provides public methods to force-emit signals containing the data that you want to send ,e.g. a singleton
  • Setup slots in objects that only accept arguments by value
  • Connect the signals to the slots in an object within the ui-thread
  • Connections must be Qt::QueuedConnection

    class timer : public QObject
    { 
    Q_OBJECT
    //... write a singleton here
    
    std::mutex mut;
    
    public signals:
    signal_tic(QString const );
    
    public: 
    void force_emit_tic(QString const s )
    {
       std::lock_guard<std::mutex> l(mut);
       emit signal_tic(s);
    }
    
    timer & ref() 
    {
      static timer This;
      return This;
    }
    private:
    timer(){}
    };
    
    // in a main thread object setup this connection
    
    connect(&timer::ref(),SIGNAL(signal_tic(Qstring 
    const)),this,SLOT(slot_accept_tic(QString const ), Qt::QueuedConnection)
    
    // In any other thread
    timer::ref()::force_emit_tic( string_when_this_happened )
    

Calling directly the singleton force-emit method results in the desired behaviour. (ofcourse objects must be properly copiable for this to work)

The reason for sending by value is that if you pass a const reference to temporary residing in another thread it's lifetime is not guaranteed. Furthermore, you need to take care of serializing the messages to the ui-thread before they actually arrive or you will eventually receive one of either incosistent data or a SIGSEGV. Qt::QueuedConnection guarantees that connections are serialized only within the memory space known to QThreads.

g24l
  • 3,055
  • 15
  • 28
  • I use a queue; when I want something updated, I pass it to a queue, one of the GUI thread's timers watches the queue, and when there are updates to make, that's the thread that makes them. I'd been doing that all along. What bit me was a legacy bit of code from ages ago. I wasn't thinking about it, wasn't looking for it, didn't even cross my mind that it might be related until I was prodded. Works great now. :) – fyngyrz Jan 20 '16 at 00:57
  • @fyngyrz no matter is your are doing a queue you have to protect that with a lock, and emit a signal to slots where arguments are passed by vale. I am just providing a method such that your application can globally address the umi thread from non ui threads. – g24l Jan 20 '16 at 07:57
  • @g241 - Queue loading and unloading is protected using a mutex. No signal is sent. If the queue contains items to be processed, the graphics thread spots that during the timer event because queue head != queue tail. It promptly unloads the items (within the context of a locked mutex) and updates them (outside the mutex, and within the context of the GUI thread.) No other signal is used. This seems to be working fine. I see no reason why it shouldn't. If there is one, and you can explain it to me, I would be very appreciative, and will award bounty to you. – fyngyrz Jan 21 '16 at 17:23
  • @fyngyrz , (1) your GUI thread blocks on the queue begs for deadlocks and makes your GUI unresponsive. Making the timer wait on the GUI thread makes your GUI responsive. (2) Lookup model-view-controller . (3) since the timer generates the event why not appropriately forward the data to the GUI thread directly, instead of signalling it , then having it wait on a blocking queue to read the data that could have been sent right from the start for the GUI to paint. It is a standard misconception in Qt that the GUI thread must do all. NO, it only has to dispatch signals and process paints. – g24l Jan 22 '16 at 11:14
  • It doesn't block on the queue. It locks the queue for exactly long enough to unload one event, assuming there' s one in there, and then goes off and does what it's told. It's lock, three local lines of c, and unlock, all in the course of normal GUI thread operations. That's it. Which affects the GUI not at all. Also, I don't signal it. The queue is the signal. The events are completely asynchronous. The only time either thread would stall would be when another thread, at exactly the same time, is doing loading or unloading, which again is just a few lines of c. Nanoseconds. – fyngyrz Jan 22 '16 at 23:49
  • @g241 Do you want me to post example code somewhere for you to look at? I think we're talking past each other, or else I'm missing something really fundamental, always possible. – fyngyrz Jan 22 '16 at 23:52
  • @fyngyrz hm, yes maybe I did not express this properly. If another thread has the lock then your gui trying to acquire it will block. If the other thread does not release your gui would be unresponsive. Have to tell you that what you try to do is best done using the at signal and slots mechanism. No need to reinvent the wheel. – g24l Jan 23 '16 at 00:15
  • Neither thread holds that lock for more than three lines of simple c code. So the max block is the time it takes to execute those lines, which again is in the low nanosecond range. The app runs as many as 15 threads; it's a real time software defined radio application. Any thread can submit a request for an update to a graphics items. There are audio and RF scopes, meters, text displays, and over a hundred control items. You [can see it here](http://fyngyrz.com/?p=915), and believe me, there is no GUI blocking going on. I thank you for your input, btw. – fyngyrz Jan 23 '16 at 04:03
  • @fyngyrz i am just saying what the best practice is. Your specific case is not known to me from the formulation of the question. Nonetheless, a deadlock, does not have to do with the length of the code but in most cases with how this code is called. At any rate you seem to take this personally, please don't , your app may just be amazing. Nevertheless, I have to advocate for best practices. Polling is not one of them, calling code directly from GUI thread is not. Thank you for your feedback, it helped improve this answer. And really, I don't care that much about the bounty or the points. – g24l Jan 23 '16 at 10:41
  • Oh, I'm not taking it personally. Just interested. Often, I learn things this way. But sometimes, "best practice" isn't actually the best way. All the signs, so far anyway, point that way here. One thing we have to watch out for is when canned design patterns are stretched to wrap around cases where they really aren't doing anyone any favors. Queues are extremely well understood, safe, and efficient inter-thread mechanisms - they do have to be well implemented, of course. In this particular situation, benefit is not apparent in making this more complicated, or slower, than it needs to be. – fyngyrz Jan 24 '16 at 08:20
  • @fyngyrz if you had applied the proper design pattern from the start then you wouldn't have run into problems like the one at question. Because the GUI would receive and handle the input during paint events , not outside. That is why QT has queuing mechanisms for the signals, that we don't have to do ourselves, and are well implemented. I am just saying correct your programs logic by doing proper and then rip the benefits. You should separate concerns, make the timer a separate object if you want and have it send signals, or do a proxy that sends signals to the GUI; they will be queued. – g24l Jan 24 '16 at 12:27
  • Well, let's see. 1st, the legacy code wasn't mine. My queuing mechanism was fine. 2nd, Qt's mechanism is slower than mine. Unless mine is actually broken, which it does not appear to be, the only demonstrated benefit (thus far, I'm still listening :) in using the design pattern you're talking about over my approach would be to make you happy. However, guaranteed consequence is less efficient software. Again, this is a real-time application - speed is paramount. 3rd, the timer was already running. There was zero other overhead added by using it, as it was already there, and had to be running. – fyngyrz Jan 25 '16 at 16:11
  • @fyngyrz , You setup is : (1) wait for timer event by polling (2) obtain mutex and block until it is released from whoever is putting staff in the queue (3) get contents of queue and block anyone who needs to add objects to the queue (4) update the GUI with the contents of the queue by emitting a signal to be received by a GUI slot . Where as the alternative design is (1) make timer emit a signal whenever it has gone off and there are objects in the queue with those objects (2) GUI is updated by receiving those objects. The alternative design has only two steps. – g24l Jan 25 '16 at 16:35
  • Okay, clearly, I've explained this poorly, because it seems you have ended up with the wrong impression of how my code works. [Here is a hopefully complete explanation of exactly what's going on.](http://fyngyrz.com/queueExample.html) If you think there is anything about this code that will cause loss of CPU time or functional problems, I would love to hear about it. – fyngyrz Jan 26 '16 at 18:47
  • @fyngyrz , the cbp_ members of MainWindow should be made a different class and appropriately be protected by the mutex through getter/setters, or at least the latter within MainWindoW. Otherwise you will end up with inconsistent data and possibly a crash, for example you counters are not atomically incremented. You are not polling which is a bit better than what I have originally understood. It would be better to make a proper consumer-producer, and have timer either to forward a list of pointers of objects to MainWindow or as an additional condition to timely check the queue. – g24l Jan 26 '16 at 19:40
  • @fyngyrz , I am not saying that your code is not working, just that you should think twice before discharging the idea of having MainWindow do work apart from that it is meant to be, which is to handle user input and output. Hope you find your way to it. – g24l Jan 26 '16 at 19:41
  • My counters aren't atomically incremented? Queue head and tail and fill are only changed inside the protection of the mutex on either side of the queuing mechanism. So there's no way for them to get out of sync. No? Did I miss something there? Also, remember: speed. Speed is paramount. Pretty means nothing. Speed means everything. The mechanisms you suggest are *slow*, so unless what is there now is broken, they are not even on the table. I'm not coding for fun here. Performance is #1. – fyngyrz Jan 27 '16 at 07:04