PDA

View Full Version : Thread freezing GUI



TheGrimace
30th May 2007, 22:00
I have a program that creates a variable number of threads to do work in the following way:


Main GUI
sub GUIs
Thread for each sub GUI
Thread that performs many io reads

This is simplified, but this is the basic structure.

My problem is this: the bottom thread freezes the top GUI.

I can't show any code (belongs to my company not me).

All of my objects are created in the run methods as they should be.

Other windows in other applications don't show any signs of slowdown so it is not the cpu.

Any Ideas?

Thank You

marcel
30th May 2007, 22:06
Do you modify/create any widgets in any of the worker threads?
How do the worker threads communicate with the corresponding GUI components?
Do you use signals/slots for this or events or you call functions across threads directly?

I'm asking because I can name a few reasons for your problem.

Regards

TheGrimace
30th May 2007, 22:11
The worker threads communicate via signals connected using the default connection type. (AutoConnect I believe).

The only functions I call directly in the threads are the start methods.

There is, however, another Widget-based plugin that I connect to sub threads whenever I create them.

Do I need to explicitly make these Queue connections?

marcel
30th May 2007, 22:17
There is, however, another Widget-based plugin that I connect to sub threads whenever I create them.
By connect do you mean at connecting them to the started() signal of a thread, I assume.

The bottom line is that no widgets must live in other thread than the GUI thread.
Qt::AutoConnection means Qt::QueuedConnection if receiving object does not live in the emitting thread, and Qt::DirectConnection otherwise.

You said that the threads are performing IO operations.
I assume that they will read something from some device and update the GUI accordingly.
A problem could be the rate at which you're updating the GUI.
Sending a lot ( and I mean a lot ) of signals from the worker threads to the GUI could put some heavy load on the GUI's event handler.

Regards

TheGrimace
30th May 2007, 22:32
Not Really.

I haven't explained it well.

I create a Main GUI and a single DoThis Widget initially. The Main GUI then creates child widgets (tabs) based on user input. Each time a tab is created, the DoThis widget is connected to the sub threads signals. These signals are usually to tell the DoThis widget to do something.

The place where the GUI hangs is during the io reads at the most sub thread. At that point the DoThis widget has yet to be called and everything is contained within the sub threads run method.

marcel
30th May 2007, 22:46
Well, some code would have been useful.
But anyway, it depends on what DoThis actually does.
If it has a slot ( or more ) connected to multiple threads, then several threads might ask it to do something, all at the same time.
Sure, the signals from the threads, translated to events, will be handled sequentially by the GUI's event handler, but as I said, it depends on what DoThis has to do when it receives a signal.

Someone posted about a similar problem some time ago, but he had a a lot of widgets in the tab widget. The problem came from the huge time needed to update the widgets contents and as well from repainting the widgets.

So, if you can provide more details, then do it.

BTW, the post is this: http://www.qtcentre.org/forum/f-qt-designer-3/t-delayed-rendering-of-qtabwidget-tabs-6902.html

Regards

TheGrimace
30th May 2007, 22:56
Basically the program works like this:

Main GUI just asks how many instances.

Each sub widget has controls to find network information.

The sub-widget creates a worker thread.

This thred creates 2 sub threads, sub thread 2 is connected (in the run method) to the DoThis widget and sub thread 1 is connected to sub thread 2

Sub Thread 1. Looks at the network and handles transmissions (parsing)and funnels commands to thread 2

Sub Thread 2. When commands are received it looks at the hard drive and finds the appropriate file. When found it emits a signal grabbed by the DoThis Widget.

The DoThis widget then creates its own thread and transmits the data.

The only time that the GUI hangs is when thread 2 is looking at the hard drive.

At no other point does the GUI hang (not even during network traffic or the DoThis widgets transmission)

Thank you for the help, sorry I can't show code.

marcel
30th May 2007, 23:09
Sub Thread 2. When commands are received it looks at the hard drive and finds the appropriate file. When found it emits a signal grabbed by the DoThis Widget.

The DoThis widget then creates its own thread and transmits the data.

The only time that the GUI hangs is when thread 2 is looking at the hard drive.


But then it makes no sense, especially if you don't communicate with other threads when you're searching for that file.
I really can't see the problem there, although it's obvious there is one.
Sorry

wysota
31st May 2007, 02:16
Thank you for the help, sorry I can't show code.

Could you provide a simple code example that works in a similar way to the copyrighted code? I mean something that creates threads and shows their relation and what they are doing. Especially the part related to "sub-GUIs" (and please explain what you mean by that term).

TheGrimace
31st May 2007, 17:53
In the Main GUI, I create an instance of DoThis GUI and pass a pointer to each tab that is created.

Each tab then passes that pointer to its QThread

PSUEDOCODE:


Each Tab's QThread Run Method:
run{
// create new objects
object_1 = new object1;
object_2 = new object2;
//each of these signals is called once per request
connect(object2,signal,object1,slot)
connect(object1,signal,object2,slot)
connect(DoThis,signal,object1,slot)
connect(object1,signal,DoThis,slot)
object2->startTCPListener();
object2->startUDPListener();
exec();
//disconnect and delete everything
}

Object1 then has a slot like the following:



readwriteLocker.lockforWrite();

listofObjects->add(new File_Thread);
uniqueID = // a new unique ID
listofObjects->at(listofObjects->count()-1)->setUniqueID(uniqueID);

readwriteLocker.unlock()
readwriteLocker.lockforRead()

for(int i=0; listofObjects->count()>i ; i++)
{
if(listofObjects->at(i)->uniqueID == uniqueID)
{
listofObjects->at(i)->start();
}
}
readwriteLocker.unlock()

The File_Thread finished signal is connected to a similar function that deletes the object.

Here is what the File_Thread run method looks like:


run{
booleanRunning = true;
find_File(); //This is where everything is done, emits, io accesses, everything
//This is also where the gui slows down and becomes unresponsive
booleanRunning = false;
}


Thank You all for your interest and help.

I hope this makes the problem more clear.

wysota
31st May 2007, 23:05
I meant something compilable...

TheGrimace
1st June 2007, 15:08
oh... sorry about that.

I probably won't have time to make that.

marcel
1st June 2007, 19:02
About that thread that is searching the hard drive(s).
You said that the GUI is freezing during that operation.
But have you noticed any other application exhibiting weird GUI interactivity while yours freezes?

Could you say at least what API are you using for searching the drive? And maybe describe a little the process?

Regards

TheGrimace
1st June 2007, 19:04
No. In fact all other GUIs have excellent response time during that period. I have checked the CPU usage, and it never gets above 10%

marcel
1st June 2007, 19:15
readwriteLocker.lockforWrite();
listofObjects->add(new File_Thread);
uniqueID = // a new unique ID
listofObjects->at(listofObjects->count()-1)->setUniqueID(uniqueID);
readwriteLocker.unlock()
readwriteLocker.lockforRead()
for(int i=0; listofObjects->count()>i ; i++)
{
if(listofObjects->at(i)->uniqueID == uniqueID)
{
listofObjects->at(i)->start();
}
}
readwriteLocker.unlock()
I might have noticed something in that code.
You create the File_Thread in some thread ( other than the GUI thread ) - thread X.
Also you create thread X in some other thread - thread Y.
Correct me if I'm wrong, but I think this is the general workflow of your app.

The problem is that the slots for a thread will be executed in the context of the thread that created it.
So, the slots of X are executed in the context of Y, the slots of Y in the context of the parent thread and so on.

The solution is to change the affinity of the threads after they are started( you are provided with the started signal ). Changing affinity for an object means that you move the event processing for that object from the current thread to a target thread.

So, for all your threads that have slots you must do the following after the thread starts:


thread->moveToThread(thread);
I am not sure, but I think this might be the problem. Or at least one of the problems.

EDIT: wherever you see slots please read events & slots! :)

Regards

TheGrimace
1st June 2007, 19:23
So I need to move the QThread object to its own run method?

marcel
1st June 2007, 19:31
No, you must call moveToThread for a thread, in it's parent thread after the subject thread starts( you will be notified by the started signal, which you must connect to slot in the parent thread ).

You always start from the GUI thread so you're guaranteed that the parent thread's ( initially GUI thread ) event processing is done it it's context( the slot connected to the child thread's started signal is executed in the parent thread's context - which is GOOD ).

By calling moveToThread you're making sure that any event( and signal ) the thread receives will be processed in the thread's context. For signals this means that the slot is executed in it's context.

Right now the child threads may block their parent thread whenever they execute a slot or they process an event, because that will be done in the parent thread. So each child thread is eligible of blocking it's parent.

I hope I made it clear.

Also, apart from this, after you create the File_Thread, you assign it with a unique ID, but then you iterate through all the threads, stating the one with that ID.
Why don't you just start the thread that you have just appended, because you know that is the new one? ( but this isn't really a problem, I was just curious why you went on the bumpier road :) ).

Regards

TheGrimace
1st June 2007, 19:41
For the starting issue, it was originally the way that you are saying, (and is that way again) I have just been trying everything I can think of to get the GUI (and a few dozen other issues) fixed.

I am sorry to keep bugging you, but I am still a little confused.

The structure exists like this:

GUI Thread
|
Control Thread
|
Tons of objects
|
File_Thread


That once Control Thread is started I move it to itself like:

run{
this->moveToThread(this);
}

or something else entirely?

I am sorry i am just not getting it. :(

Thank You for all of your help

marcel
1st June 2007, 19:48
OK.
Maybe this helps you a bit:


void QObject::moveToThread ( QThread * targetThread ) Changes the thread affinity for this object and its children. The object cannot be moved if it has a parent. Event processing will continue in the targetThread.
To move an object to the main thread, use QApplication::instance() to retrieve a pointer to the current application, and then use QApplication::thread() to retrieve the thread in which the application lives. For example:
myObject->moveToThread(QApplication::instance()->thread()); If targetThread is zero, all event processing for this object and its children stops.
Note that all active timers for the object will be reset. The timers are first stopped in the current thread and restarted (with the same interval) in the targetThread. As a result, constantly moving an object between threads can postpone timer events indefinitely.
Warning: This function is not thread-safe; the current thread must be same as the current thread affinity. In other words, this function can only "push" an object from the current thread to another thread, it cannot "pull" an object from any arbitrary thread to the current thread.

Anyway, you should do something like this:


void ParentThread::some_method()
{
childThread = new Thread();
connect( childThread, SIGNAL( started() ), this, SLOT( childStarted() ) );
childThread->start();
}

void ParentThread::childStarted()
{
childThread->moveToThread( childThread() );
}
I assume you can find a way to identify the last started child thread, so you can use it in childStarted().

Regards

marcel
4th June 2007, 20:31
Well, did it work?
It often helps to reply and let us know if it really worked.

TheGrimace
4th June 2007, 20:46
Sorry, I got sidetracked by a memory error and forgot to reply.

This worked like a charm.

I really appreciate the help.

Thank You

dcss-design
22nd June 2007, 00:58
Hello all,

I stumbled across this thread due to having a similar problem. I have read and mostly understood all of this but haven't resolved my GUI freeze. Thank you to all those that have posted and given me a better understanding of some do's and don'ts in Qt. My scenario seems slightly different however.

I have two versions of this application, Windows and Linux. The Windows version doesn't exhibit the behaviour. The source code is identical between each except for the sound interface. There are roughly 6 threads in each application. The sub threads never freeze or get stuck (I have counters to watch them from a monitor/timer thread.) Even when the GUI freezes the sound can still be heard, Ethernet Packets can be sent and received, the obvious indication of GUI freeze is the buttons will no longer refresh themselves or respond to being pressed, and the Timer SLOT in the GUI thread stops getting called. The Timer SLOT sets a semaphore each time it is called and one of the other threads is released by this semaphore to update all timing operations. The tryAcquire Qt library function times out and posts a message that the timer didn't fire. Once the timer quits firing, it never comes back and the message is printed continuously until the app is shutdown. This occurs somewhat randoming from when the app is started. Sometimes the app never gets fully started, sometimes it runs for long time.

Sound card is the issue:
In the Windows version I have two threads for sound. One waits for signals to feed the soundcard samples for playback, and the other waits for signals to grab microphone data. No issues.

The Linux version uses the ALSA driver. When I don't start the sound driver in the Linux version my GUI doesn't ever seem to freeze. The ALSA version uses the CALLBACK mechanism to know when to move audio. This is where I assume the issue is but I don't quite follow why, or how best to solve it.

The GUI thread starts the ALSA driver and sets up the CALLBACKS. It had been my assumption that the CALLBACKS were actually executed by lower level (soundcard, kernel, driver level no real clue here) routines, but it may be that by setting up the CALLBACKS from the GUI thread I somehow must interfere with it. I tried creating a separate thread in the Linux version, calling the moveToThread function mentioned previously on it (which I do on all of my threads now thanks to this post), and using it to start up the Linux Sound System. This had the same lack of effect. The ALSA callbacks essentially only interact with a single queue each of sample data each. Other threads handle that data and tickle the GUI as required. I've used this audio mechanism in many other apps with no issues.

I believe that I have all of the GUI elements properly isolated from the sub threads by signals and slots. The sub threads call public routines that simply emit a signal to cause the GUI thread to change when it can get to it.

Does anybody have any ideas on this? I have pretty well exhausted my thoughts now other than to rewrite the ALSA routines to not use callbacks and use polling threads for both playback and record similarly to the DirectX version in Windows.

Thanks to all,
Doug

marcel
22nd June 2007, 19:48
The sub threads call public routines that simply emit a signal to cause the GUI thread to change when it can get to it.

Do you mean that from a worker thread you call a public method from the GUI thread that emits a signal connected to a slot in the GUI thread?
Well, here is your problem. The slot from the GUI thread will execute in the worker thread(direct connection). And of course if that slot affects some widgets then you will crash the GUI event loop.
This is identical with modifying a widget directly from a worker thread.

So the solution is to emit a signal from the worker thread instead of calling the GUI thread method directly. This signal should be connected to the very same slot that you already have in the GUI thread. The connection will be queued and the slot will execute asynchronously, when the event loop enters.

Regards

dcss-design
24th June 2007, 18:00
Marcel,

Over the last couple of days, I have read some additional implementation information on the communication between threads and using the signal/slot mechanism to do this. I follow what you are saying but it seems contradictory with what I have read in the Qt documentation.

The connect documentation and Qt::AutoConnection state:
"If the signal is emitted from the thread in which the receiving object lives, the slot is invoked directly, as with Qt::DirectConnection; otherwise the signal is queued, as with Qt::QueuedConnection."

By putting both the signal and the slot in the GUI class, calling a member function of the GUI class to emit the signal (and set some variable) from another thread, I should be getting a queue response because the signal is being emitted from a thread which is different from the one where the receiving object is? Am I reading this wrong, that is certainly possible? Just by virtue of the thread in which the SIGNAL is declared, not called, does that change this? Could I also just set these connections up and force them to be queued, since they are unique? My issue is that I would have create these signals and connections in several threads rather than one set of functions in the GUI thread that could be used by all external threads. I can certainly do this, I just want to be sure it is required?

Beyond that, before I knew anything about not modifying the GUI from non-GUI threads, on the Windows version I got debug messages saying "can't modify xxxx from non-GUI thread." Setting this mechanism up removed the error message, maybe it isn't a fix.

Further, the Windows version of the app has no problems, only the Linux version does. When I don't run the sound routines in Linux, the GUI doesn't lock up. The GUI does nothing different in this case, I simply don't have audio. I drive the same queues as before at the same rates. Nothing other than normal startup has to occur for my GUI to freeze.

For other reasons I have been playing with the frequency of the CALLBACKS from ALSA and have noticed that the more frequent the CALLBACKS are occuring, the more quickly my GUI freezes after startup. A 5ms callback rate will freeze within 10 seconds of startup 3 of 5 times. A 10ms callback rate doesn't ever seem to freeze within the first 5 minutes of execution.

Thus my supposition that the CALLBACKs from ALSA, are causing some issue with the GUI thread. I could very well be wrong and your reply may be my issue. I would like to think that it is since I could solve the problem and move on to other work, but it doesn't quite jive with all the data.

Please let me know your thoughts and tell me the error of my understanding on the SIGNAL/SLOT multi-thread communications? In addition, can you tell me what thread is getting interrupted by the callbacks from ALSA, or is it none and being executed by the ALSA driver? My intent of the next couple days is to rewrite the ALSA sound routines to be polled from their own threads and see if that helps me.

Thanks for the thoughts and I look forward to any other advice you can give,
Doug

marcel
24th June 2007, 18:30
The connect documentation and Qt::AutoConnection state:
"If the signal is emitted from the thread in which the receiving object lives, the slot is invoked directly, as with Qt: DirectConnection; otherwise the signal is queued, as with Qt::QueuedConnection."
Yes, but this connection is made when you call connect, and not derived dynamically, when you emit the signal.

So the connection remains Qt:: DirectConnection, even if you call the method that emits the signal from another thread.



By putting both the signal and the slot in the GUI class, calling a member function of the GUI class to emit the signal (and set some variable) from another thread, I should be getting a queue response because the signal is being emitted from a thread which is different from the one where the receiving object is? Am I reading this wrong, that is certainly possible? Just by virtue of the thread in which the SIGNAL is declared, not called, does that change this? Could I also just set these connections up and force them to be queued, since they are unique? My issue is that I would have create these signals and connections in several threads rather than one set of functions in the GUI thread that could be used by all external threads. I can certainly do this, I just want to be sure it is required?
No, it gets a direct connection. You could try to force it to QueuedConnection when you call connect but I'm not sure if it will work.
Being a DirectConnection, the slot will get executed in the context of the worker thread.
So if any widgets are modified as a result of this slot, the GUI will block.

But why don't you try my suggestion?
Instead of a GUI method call, just emit a signal from the worker thread. The signal should be connected to the slot in the GUI. There aren't that much modifications to be made( actually they are minimal ).
This way you are guaranteed to have a QueuedConnection, therefore no blocking.

I hope you understand.



For other reasons I have been playing with the frequency of the CALLBACKS from ALSA and have noticed that the more frequent the CALLBACKS are occuring, the more quickly my GUI freezes after startup. A 5ms callback rate will freeze within 10 seconds of startup 3 of 5 times. A 10ms callback rate doesn't ever seem to freeze within the first 5 minutes of execution.

Yes, a perfectly good explanation for blocking the GUI. Therefore use a signal instead of calling the GUI thread directly. It is not normal to do that.



Thus my supposition that the CALLBACKs from ALSA, are causing some issue with the GUI thread. I could very well be wrong and your reply may be my issue. I would like to think that it is since I could solve the problem and move on to other work, but it doesn't quite jive with all the data.

No, only the context in which the GUI slot is executed is the problem.

Regards

dcss-design
24th June 2007, 18:57
Thanks Marcel,

I hadn't thought about it being more about the connect() and when it was done, than when the signal was actually emitted. Basic flaw in my understanding and reading of the documentation.

I'll give it a try, see what I get, and report back.

Thanks,
Doug

dcss-design
27th June 2007, 16:57
Marcel,

Before making the changes you suggested, I did a little testing with the debugger. Turns out my method is just fine for creating signals from other threads. In each case that I called one of the public functions in my GUI class to emit the signal, when the GUI update was actually performed it was in fact performed by the GUI thread, not the thread that called the public function causing the signal to be emitted. This was verified in both Windows and Linux.

I further verified this fact with Trolltech. They stated: "because when the signal is emitted the code in the QMetaObject::activate() will check that the thread that resulted in the emission of the signal is different from the thread in which the object lives. If this is the case, an event will be posted to the event loop of the receiver and will be be processed asynchronously so this is safe."

In any event, I'm back to square one. I rewrote my ALSA routines to be separate threads (one for playback and one for record) that use blocking calls to move the data. Excactly the same external interface to the rest of my application. No GUI lockups now. Even more surprising is that the audio performance and latency is much better.

At this point, I'm going to put this on the back burner and expect I'll find the root cause at some point while doing something else. If I do, I'll post on it.

Thanks for all the suggestions, I certainly learned a lot about some things I didn't know much about before.

Regards,
Doug