Solved GUI operations across threads (again)
-
@kshegunov said:
Qt's objects have that message interface as well.
QCoreApplication::sendMessage
andQCoreApplicaiton::postMessage
.Yes, but how do you send a QPushButton::setText ?
Your thread is not supposed to know of the existence of push buttons at all.
Why not ? Why should I need slots that do nothing useful except call basic functions ?
Please understand that I'm coming from another context and from a general-purpose OS, whose SDK, while ugly, is really flexible. With Qt multi-threading I don't have this freedom. I feel that the developers are imposing their own paradigms of what is multi-threading by force of action.
Multi-threading in Qt currently has an overhead and a complexity. I understand that changing this would require lots of development work and may not be justified because there are not that many heavily multi-threaded graphical applications using Qt.
For myself, my future is now settled : I'm going to be writing lots and lots of calls to invokeMethod.
-
@Harry123 "Why not ? Why should I need slots that do nothing useful except call basic functions ?" to separate logic from UI. @kshegunov already said: decoupling. Why should your logic (business logic) know UI details? Both are not related to each other and should be decoupled. This is nothing Qt specific, it's software design.
-
Yes, but how do you send a QPushButton::setText ?
By posting a meta call event, like done here, which is ultimately what
QMetaObject::invokeMethod
does when used with queued type of connection.Why not ? Why should I need slots that do nothing useful except call basic functions ?
The punchline is the last sentence of my previous post. Basically, because that creates a spagetti C code where everyone knows about everyone else( and thread access serialization is done by hand, but that's a minor detail).
Please understand that I'm coming from another context
I gathered that.
and from a general-purpose OS
The world doesn't begin with windows, and it certainly doesn't end there. Linux and OSX are no less "general-purpose".
whose SDK, while ugly, is really flexible.
It's a matter of opinion and of some debate, but I don't want to get into it, as stated.
With Qt multi-threading I don't have this freedom. I feel that the developers are imposing their own paradigms of what is multi-threading by force of action.
Not at all, you can still derive from
QThread
overrideQThread::run
and do the synchronization by hand, no one is stopping you from doing that. The only thing that's of matter in this case is that the GUI is not thread-safe or reentrant, so it's your responsibly as with any object/library that is not thread safe by default to work around it.I understand that changing this would require lots of development work and may not be justified because there are not that many heavily multi-threaded graphical applications using Qt.
It most probably won't change, as Qt is not a MS windows only toolkit. And there're considerations going beyond what the win API might or might not provide.
Also there are enough Qt applications using threads, most of them with OpenGL which can be threaded by design. For those that use widgets and don't employ opengl the GUI thread is pretty much enough, as the heavy lifting (i.e. calculations, processing and the such) is moved to worker threads and the GUI is only signaled when changes should be reflected.
-
the GUI is only signaled when changes should be reflected
Well, that's the paradigm. To be clear, I'm not saying that GUI operations should work across threads, just that some basic functions could do their own invokeMethod so I wouldn't have to code that much :;
When all is said and done - all I wanted was to shake some dust out of some paradigms, even if that wouldn't help me now.
-
Hi, just a side note.
Using lambdas for slots
can reduce the coding for signal&slot to some
degree for those trivial cases.Also, those gui call you have now.
That would be like
windows API call with handles to the widgets ?
or was it possible in qt3 to pause main loop
and do directly ui->label->setText() ? -
@mrjj :
Lambdas would require C+11, not so ? I would prefer to require as little as possible.
And yes, in qt3 I found just such examples. This approach would work for most modern OS, since qt3 did work across many environments, but maybe Qt developers later encountered some OS that were more limiting.
-
@Harry123
yes it does. But even for small embedded boards the compiler can do c++11
(often) so not sure it still makes sense to avoid. Even I do get your point.Well I have old kylix program in linux. they used Qt3 for that.
I do have that sync function and its not a good solution as if your worker threads
are very busy updating/talking to GUI, the whole app get slow as each time it
suspend main loop. So while convenient back then, i have issues
with it now as we have 3 times the number of threads and
they all talk and pause main loop.So while you have my sympathies
for having to port it all,
it might not even have worked really well anyway. :) -
@Harry123 OK, so your application's current model is as follows:
Thread N: User code -> Call windows API (say SetText on button) -> SendMessage(HWND for button,WM_SETTEXT)
GUI Thread: --> PostMessage(HWND for button,WM_SETTEXT)
The equivalent in Qt is:
Thread N: User code -> QMetaObject::invokeMethod(button,"setText",text);
GUI Thread: --> Button::setText(text)
Since you're just doing simple property setters, I'd suggest trying invoke method first. The only problem with invokeMethod as I see it is it isn't statically typed. But then again, neither is the Windows API.
-
@cheezus :
setText was just a simple example given for the discussion.
I had a little look at some Qt headers, and Q_INVOKABLE is not used very much with the method declarations.@mrjj :
The program performs fine on Windows, since the majority of SDK calls are just messages, others do work across threads, while only some require freezing for an unnoticeable small time. And thanks for your much needed sympathies.
-
@Harry123 said:
just that some basic functions could do their own invokeMethod so I wouldn't have to code that much
They could, but they shouldn't. In 99% of cases that'd be a terrible overhead for a simple property change. As established, use
QMetaObject::invokeMethod
to work around it.Lambdas would require C+11, not so ?
Qt 5.7, I believe, will require C++11 compatible compiler, so in a few months it wouldn't matter anyway.
I had a little look at some Qt headers, and Q_INVOKABLE is not used very much with the method declarations.
Possibly because everything that's declared as a slot is already invokable. So that macro appears only for non-slot members that should be known to the meta-object system.
while only some require freezing for an unnoticeable small time
As I said, it's possible to block the event loop, but you never said from which thread you want to do that. In any case, here's the simplest way of doing it:
class EventLoopBlocker : public QObject { Q_OBJECT public: EventLoopBlocker(QObject * parent) : QObject(parent), lock(0), waiter(0) { } Q_INVOKABLE void block() { waiter.release(); lock.acquire(); } void unblock() { lock.release(); } void wait() { waiter.acquire(); } private: QSemaphore lock; QSemaphore waiter; }
Usage is simple:
- Create the object in the main/GUI thread (for example as child of QApplication).
- Block the event loop (from thread other than the main one):
EventLoopBlocker * blocker; //< You can pass the pointer when you create the threads. QMetaObject::invokeMethod(blocker, "block", Qt::QueuedConnection); blocker->wait();
- Unblock the event loop (from thread other than the main one obviously):
EventLoopBlocker * blocker; //< You can pass the pointer when you create the threads. blocker->unblock();
This is all there is to it. However @mrjj's note on responsiveness applies.
PS.
The sample code will work with one thread controlling the GUI thread, for universal solution (i.e. that takes into accounts multiple calls toblock()
) you should fiddle a bit with the logic and the samaphores. -
Thanks for this very ingenious freeze function.
everything that's declared as a slot is already invokable
This is the missing piece in the puzzle, which will minimize the required code much more.
Many thanks to you all for your much appreciated help.