PDA

View Full Version : QThread communication without signal and slots



forrestfsu
21st May 2007, 18:51
I have a worker thread that reimplements QThread's run() function. Within run() I need to update elements on the GUI. However, every tip I see so far is to use Signals/Slots to let the GUI know to update the screen. I don't want to do this because it sounds like using signals/slots would throw my desired update into a queue where it would sit until the main event loop rolled around. I'm simulating live video, so I need the worker thread to be able to call a function directly, from the main GUI thread.

However, when I do it the way I want, my program crashes when the user starts moving the mouse around. Do you have any pointers on how to accomplish my goal?

Some snippits of my current code:


void VideoStreamEngine::run()
{
Image *img = NULL
while (true)
{
server->GetFrame(&img);
pParent->SetImage(img); // This is the GUI function that I want to call instead of using signals/slots
}
}


void VideoRenderer::SetImage(Image *img)
{
mutex.lock();
if (img != NULL)
{
image = // Some code to scale image into a 32-bit QImage

// Set QPixmap
screenLabel->setPixmap( QPixmap::fromImage(*image) );
}
mutex.unlock();
}


A Note: while digging through the docs...is "Qt::DirectConnection" what I am looking for?

marcel
21st May 2007, 19:06
Calling the GUI thread function like that is equivalent of not using a worker thread at all.

I have thought of one solution( other video apps seem to do it ): frame buffering ( actually a queue - something from the producer/consumer chapter :) ).
The frame buffer should be shared between the GUI and worker threads and the access to it should be synchronized with a mutex.

Amyway, the GUI thread should read data while it is available( if not, it should wait ), and take what it reads from the buffer. On the other side, the worker thread pushes frames in the buffer.

You should set a reasonable buffer size ( probably based on frame resolution - minimum 2-4 mb ).

Sending frame by frame will take you nowhere. But that is just my opinion.
Maybe the guys have a better idea.

Regards

marcel
21st May 2007, 19:10
Anyway, there is no need to use a mutex there because no one else is accessing that data.
There is no one else to protect it from.
The worker thread generates one frame and the GUI comes and draws it. And that is it.

Regards

marcel
21st May 2007, 19:17
A Note: while digging through the docs...is "Qt:DirectConnection" what I am looking for?


No :).
Direct connection is exactly what you need to avoid! And anyway, this is kinda what you're using now. Connecting to the GUI thread through direct connection means that you're executing the GUI thread slot in the context of the worker thread - which again is not good because you're modifying a widget inside.

Try thinking of what I proposed to you. BTW - you have to implement that using queued connections.

Regards

forrestfsu
21st May 2007, 19:49
Thanks for the pointers Marcel. Let me attempt to clarify what the Thread is actually doing. There is a whole lot more going on then the snippit I put up. The point of the thread was because the "server->GetFrame(&img);" call hangs until a new frame is available. Because of this, I wanted it to be in it's own thread. As far as creating a buffer, yes we have done this and it's intended for video playback. However, we have a "Live Video" mode which is what I'm currently talking about. The "GetFrame" function will return the latest available image. So the point is not to keep any sort of buffer, but just to display the last available frame as fast as possible. It's weird, with the code I displayed earlier: it will display the video perfectly fine until I start moving the mouse around...and then the terminal throws out a bunch of error messages like "X Error: BadRequest..."

wysota
21st May 2007, 20:14
You can't do it like this and using a mutex won't help. You might be calling setPixmap() the same moment when the other thread calls pixmap(). You have to set the image from the other thread. You can use custom events to deliver the pointer or store it in some synchronized variable and signal the main thread to pick it up.

marcel
21st May 2007, 20:22
o the point is not to keep any sort of buffer, but just to display the last available frame as fast as possible. It's weird, with the code I displayed earlier: it will display the video perfectly fine until I start moving the mouse around...and then the terminal throws out a bunch of error messages like "X Error: BadRequest..."


That is because you're modifying a widget from another thread.
I told you that function will be executed in the context of the worker thread. And widgets can be modified only by the GUI thread.
Try passing it through a signal to the GUI thread, or some custom event, because this is not the way to go.

forrestfsu
21st May 2007, 21:26
Thanks guys, I think I'm following what you are saying. I just wanted to make sure that you both realized that the two functions I wrote are in two separate classes, one in the worker thread and one in the GUI thread. So, I was not really doing any paint calls in the worker thread. Are you saying that I shouldn't be calling SetImage, because SetImage's function belongs to a QWidget which is part of the main GUI thread? In that case, yes I can use Signal's and Slots to let the GUI thread become aware that it needs to do work. However, I was trying to stay away from Signal's and Slot's because I wanted to directly call the function instead of putting it in a queue.



// VideoStreamEngine is on "Worker Thread"
void VideoStreamEngine::run()
{
Image *img = NULL
while (true)
{
server->GetFrame(&img); // hangs until a frame is available
pParent->SetImage(img); // This is the GUI function that I want to call instead of using signals/slots
}
}
// -----------------------------------------------------
// VideoRenderer class is on "GUI Thread"
void VideoRenderer::SetImage(Image *img)
{
if (img != NULL)
{
image = ... // Some code to scale img into a 32-bit QImage

// Set QPixmap
screenLabel->setPixmap( QPixmap::fromImage(*image) );
}
}

wysota
21st May 2007, 21:35
You can't call it directly because the other thread can be in an unpredictable state at that time. The point of queuing the request is to synchronise both threads.

marcel
21st May 2007, 21:36
I just wanted to make sure that you both realized that the two functions I wrote are in two separate classes, one in the worker thread and one in the GUI thread.

Well, yes we did :).
They are in two separate threads, but the way you call them is wrong. You might as well call screenLabel->setPixmap from the worker thread. You would get the same result.



So, I was not really doing any paint calls in the worker thread

Not explicitly, but setting the pixmap will trigger a paint event.
QWidgets are not reentrant, nor thread safe. That means you cannot access/modify them from two threads.
While you access them in one thread, the widgets may process and event in the GUI thread and possibly change their member data, compromising your other thread.



However, I was trying to stay away from Signal's and Slot's because I wanted to directly call the function instead of putting it in a queue.

You shouldn't notice any delay or lags if you do things right.



I think I'm following what you are saying

Then why didn't you modified your code? :)

Michiel
21st May 2007, 21:47
Calling a function 'in another thread' is impossible by definition. Threads can split, merge and even synchronize, but they're like distinct processes that happen to share the same memory.

The only way to communicate is to set data somewhere where you know the other thread is going to find it. That is what signals and slots do.

Threads work this way for a reason. You wouldn't want to interrupt the other thread in the middle of a computation, would you?

wysota
21st May 2007, 21:53
Not explicitly, but setting the pixmap will trigger a paint event.
You don't have to paint and paint event is not a danger here at all, because it'll be scheduled for the right thread. The problem is that you modify the object data while something else might be accessing it.


Calling a function 'in another thread' is impossible by definition. Threads can split, merge and even synchronize, but they're like distinct processes that happen to share the same memory.

The only way to communicate is to set data somewhere where you know the other thread is going to find it. That is what signals and slots do.

They share code and data and in theory in some cases you could probably access the stack and change the return address of the current frame :)

forrestfsu
21st May 2007, 22:04
Thanks to the both of you. I went ahead and used signals and slots and I don't see any delay in the video so far.

Michiel
21st May 2007, 22:28
They share code and data and in theory in some cases you could probably access the stack and change the return address of the current frame :)

Yeah. You could also access the private member variables of a strange class by using pointer arithmetic. :p

wysota
21st May 2007, 22:57
Yeah. You could also access the private member variables of a strange class by using pointer arithmetic. :p
There are easier ways to do that. Even Qt does that, by the way :)

Michiel
21st May 2007, 23:27
Really? Well, I wouldn't know. I only write nice code. :rolleyes:

Qt just went down a notch in my book. Private member vars are private for a reason.

There. My joke reply started a serious discussion. :)

wysota
22nd May 2007, 00:43
Private member vars are private for a reason.

And they violate that rule for a reason as well. So far I have only seen one occurence of it in Qt code. Maybe there are more or maybe there aren't any now that Qt 4.3 arrives.