Keeping the UI alive
-
wrote on 5 Apr 2025, 02:45 last edited by
Hi there,
I have a QT 5.15 application that runs under Linux on an stm32mp157 based SOM from Digi. Has a 320x280 TFT display, 4x7 membrane keypad and an Xbee3 for communicating with other nodes. The display is managed by wayland/weston and I have a number of windows that the user sees to implement the UI. The screens are all navigated with key-presses which are signaled from a timer handler created by MainWindow. The timer handler wakes up every 100ms, scans the key-matrix and emits a signal if a key is pressed. The window objects connect to the keypress signal, one at a time of course, whichever is active, and process the keycodes appropriately. All works really well and I must say, for my first exposure to QT, I am really impressed by it!
The problem: When the user initiates a unit of work that requires me to send data over the Xbee, the screen disappears and the user sees the underlying weston desktop. Clearly this is because the code ends up blocked down in QSerialPort somewhere (actually inside the Linux serial subsystem I would suspect). So the question is what is the best solution to keep the GUI running. What thread is the keypress signal handler running on? When a signal is handled by a slot connected to it, does it run on the signaler's thread, i.e. the timer handler thread in this case? Or does emit() cause a new thread to be created for the slots connected to it?
Anyway I'm just looking for the best way to hand-off the work of talking to the XBee to something that won't starve the UI while it is busy chugging away at 9600 baud ...
-Dallas
-
Hi there,
I have a QT 5.15 application that runs under Linux on an stm32mp157 based SOM from Digi. Has a 320x280 TFT display, 4x7 membrane keypad and an Xbee3 for communicating with other nodes. The display is managed by wayland/weston and I have a number of windows that the user sees to implement the UI. The screens are all navigated with key-presses which are signaled from a timer handler created by MainWindow. The timer handler wakes up every 100ms, scans the key-matrix and emits a signal if a key is pressed. The window objects connect to the keypress signal, one at a time of course, whichever is active, and process the keycodes appropriately. All works really well and I must say, for my first exposure to QT, I am really impressed by it!
The problem: When the user initiates a unit of work that requires me to send data over the Xbee, the screen disappears and the user sees the underlying weston desktop. Clearly this is because the code ends up blocked down in QSerialPort somewhere (actually inside the Linux serial subsystem I would suspect). So the question is what is the best solution to keep the GUI running. What thread is the keypress signal handler running on? When a signal is handled by a slot connected to it, does it run on the signaler's thread, i.e. the timer handler thread in this case? Or does emit() cause a new thread to be created for the slots connected to it?
Anyway I'm just looking for the best way to hand-off the work of talking to the XBee to something that won't starve the UI while it is busy chugging away at 9600 baud ...
-Dallas
wrote on 5 Apr 2025, 07:38 last edited by JonB 4 days ago@Dallas-Posey
I would like to try to answer briefly, in the hope it may be of some use. I am afraid I know nothing about devices, realtime or embedded systems. I just want to clarify how Qt for, say, a desktop system works.You have a main thread. That handles all the UI output, and also any keypress inputs. Qt is not creating multiple threads for you. The UI remains responsive so long as it is not off performing some time consuming computation.
If threads are involved at all, where a slot runs depends on how it has been
connect()
ed to the signal and which thread(s) the signalling and slot object(s) live in. By default, if signalling object and slot object are both in the same thread then it defaults toDirectConnection
, meaning that the slot runs in same thread as the signaller and in fact it is called immediately as a direct function call. If they are in different threads then the default isQueuedConnection
: the signal is emitted from its thread but placed in queue. When the main Qt event loop is next entered the queued signal's slot is run in the thread of the slot object. Have a read of https://doc.qt.io/qt-6/threads-qobject.html#signals-and-slots-across-threads. Qt does not auto-create new threads for you, and itsemit
keyword us actually#define
d to the empty string, so it does nothing at all in itself and is merely there for you as a programmer to "look at".As you describe the situation I do not see that you would need to create any threads at all, at least in my knowledge of a desktop version. You might use a blocking serial port call and hence need a thread in a "traditional" Linux application, but Qt's
QSerialPort
functions asynchronously so you can just use it from the main thread, acting on itsreadyRead()
signal without blocking the UI.The time you do need a thread is only if you want to do heavy computation during a slot on the main thread. For example, if in response to a serial byte arriving or similar you want to calculate the first one million prime numbers that would indeed block the UI, and you could move the computation off to its own thread.
As I said, I do not know whether this helps or applies to your particular situation or whether that has something very different going on, but maybe it gives some clarity.
-
@Dallas-Posey
I would like to try to answer briefly, in the hope it may be of some use. I am afraid I know nothing about devices, realtime or embedded systems. I just want to clarify how Qt for, say, a desktop system works.You have a main thread. That handles all the UI output, and also any keypress inputs. Qt is not creating multiple threads for you. The UI remains responsive so long as it is not off performing some time consuming computation.
If threads are involved at all, where a slot runs depends on how it has been
connect()
ed to the signal and which thread(s) the signalling and slot object(s) live in. By default, if signalling object and slot object are both in the same thread then it defaults toDirectConnection
, meaning that the slot runs in same thread as the signaller and in fact it is called immediately as a direct function call. If they are in different threads then the default isQueuedConnection
: the signal is emitted from its thread but placed in queue. When the main Qt event loop is next entered the queued signal's slot is run in the thread of the slot object. Have a read of https://doc.qt.io/qt-6/threads-qobject.html#signals-and-slots-across-threads. Qt does not auto-create new threads for you, and itsemit
keyword us actually#define
d to the empty string, so it does nothing at all in itself and is merely there for you as a programmer to "look at".As you describe the situation I do not see that you would need to create any threads at all, at least in my knowledge of a desktop version. You might use a blocking serial port call and hence need a thread in a "traditional" Linux application, but Qt's
QSerialPort
functions asynchronously so you can just use it from the main thread, acting on itsreadyRead()
signal without blocking the UI.The time you do need a thread is only if you want to do heavy computation during a slot on the main thread. For example, if in response to a serial byte arriving or similar you want to calculate the first one million prime numbers that would indeed block the UI, and you could move the computation off to its own thread.
As I said, I do not know whether this helps or applies to your particular situation or whether that has something very different going on, but maybe it gives some clarity.
wrote on 7 Apr 2025, 13:25 last edited by@JonB Thank you! That was a very informative and helpful response. So the MainWindow is running on the main thread. It starts a timer connected to a handler,
scanKeyPad
which signalskeyCode
when a key is pressed. When the timer expires I assume thatscanKeyPad
runs on a new thread and any object created by MainWindow with a slot that is connected to signalkeyCode
gets queued and runs in it's turn on the main thread? Anyway, again, your answer is both helpful and intriguing and I will read further (as you suggest).-Dallas
-
@JonB Thank you! That was a very informative and helpful response. So the MainWindow is running on the main thread. It starts a timer connected to a handler,
scanKeyPad
which signalskeyCode
when a key is pressed. When the timer expires I assume thatscanKeyPad
runs on a new thread and any object created by MainWindow with a slot that is connected to signalkeyCode
gets queued and runs in it's turn on the main thread? Anyway, again, your answer is both helpful and intriguing and I will read further (as you suggest).-Dallas
wrote on 7 Apr 2025, 13:32 last edited by Pl45m4 4 Jul 2025, 13:35@Dallas-Posey said in Keeping the UI alive:
When the timer expires I assume that scanKeyPad runs on a new thread and any object created by MainWindow with a slot that is connected to signal keyCode gets queued and runs in it's turn on the main thread?
No, a
QTimer
does not create or run separate threads.
A simpleQTimer
is asynchronous but still a single thread solution.
(Same as when using Qt's Signal & Slot mechanism).Both are handled in the Qt Event Loop of your app's main thread (that thread that holds your
Q(Gui)Application
).
Unless you create a newQTimer
object in a separate thread explicitely, of course. -
@JonB Thank you! That was a very informative and helpful response. So the MainWindow is running on the main thread. It starts a timer connected to a handler,
scanKeyPad
which signalskeyCode
when a key is pressed. When the timer expires I assume thatscanKeyPad
runs on a new thread and any object created by MainWindow with a slot that is connected to signalkeyCode
gets queued and runs in it's turn on the main thread? Anyway, again, your answer is both helpful and intriguing and I will read further (as you suggest).-Dallas
wrote on 7 Apr 2025, 14:07 last edited by JonB 4 Jul 2025, 14:09@Dallas-Posey said in Keeping the UI alive:
When the timer expires I assume that scanKeyPad runs on a new thread
All as @Pl45m4 has written above. Like I said, honestly unless you create them yourself Qt does not create threads for you "behind the scenes". At least mostly, for your purposes, and keeping it simple, before I get into hot waters. Don't assume there is any threading going on.
-
wrote 30 days ago last edited by
Got it. I think what kills my UI is that I have to inject an inter-character delay when I write to the serial port, this being required by other nodes in the XBee network. So I write a char, usleep(5000), write another ... until done. Unfortunately the remote nodes are pretty slow and won't work with less than 5ms between octets. I need to get that usleep outta there somehow ...
-
Got it. I think what kills my UI is that I have to inject an inter-character delay when I write to the serial port, this being required by other nodes in the XBee network. So I write a char, usleep(5000), write another ... until done. Unfortunately the remote nodes are pretty slow and won't work with less than 5ms between octets. I need to get that usleep outta there somehow ...
@Dallas-Posey You can use QTimer instead of waiting. Then it should also work in main thread. Or you move the serial communication into a thread. But I would first try the QTimer approach because it is easier to implement.
-
wrote 29 days ago last edited by
Thanks @jsulm I'll give it a go immediately!
1/8