PDA

View Full Version : QThread slots not executed in created thread context?



bobrien
13th September 2011, 02:32
Hello All,

I'm having some difficulty getting slots residing in a QObject class that has been moved into a thread to actually execute in a separate thread.

I have read quite a bit on this, and found most examples, including the one here (http://labs.qt.nokia.com/2010/06/17/youre-doing-it-wrong/) to be slightly simplified....or at the very least different than my program.

I have (essentially) a UI that displays streaming video. The video is received from the camera, processed and marked up. It is then finally displayed on the UI (on a QLabel). The system is 100% functional. However, I've realized that the speed of exposing and transfering the images from the camera at a high rate (>30fps) is causing substantial delay in my UI responsiveness. The processing part is "correctly" threaded to the point that it doesn't affect UI performance (although it may be re-evaluated if I solve this issue) - I mention this because it was done using the older, and supposedly now frowned upon method of subclassing QThread and re-implementing run().

Where this appears to be different from the examples provided is that rather than creating the QThread objects in some sort of main() function, I create them in the constructor for my QMainWindow object. Then I want to connect a signal from the object I hope to move to the thread, to another object that remains in the current (main) thread. Something like:



class WindowApp : public QMainWindow
{
Q_OBJECT

WindowApp()
{
myObject = new myObject();
otherObject = new OtherObject();

QObject::connect(myObject, SIGNAL(mySignal()), otherObject, SLOT(handleMySignal()));
camera_thread.start();
myObject->moveToThread(&camera_thread);
}

private:
QThread camera_thread;
MyObject* myObject;
OtherObject* otherObject;
};

class MyObject : public QObject
{
Q_OBJECT

MyObject();

signals:
void mySignal;
};

class OtherObject:: public QObject
{
Q_OBJECT

OtherObject();

public slots:
void handleMySignal();
};


The result of running this code is a completely functional application, except that when examining the process ID's of the various functions/slots being called, I can see that (in particular) the slot handlyMySignal() is being executed in the same thread as both the WindowApp constructor and other (not shown here) slots in MyObject.

After quite a bit of reading, I believe this to be because the QThread camera_thread is owned by WindowApp (which is effectively my "main" thread). The net result is anything happening in handleMySignal() causing the UI to be delayed, due to (I believe) the connection type being modified from queued to direct.

So my question, finally, is in this scenario how can I define a new thread, move an object to it, AND have that objects slots be executed in the new threads context?? This seems like it shouldn't be that hard, or that uncommon, yet I am stumped.

Thanks in advance,

Barry

wysota
13th September 2011, 10:28
The result of running this code is a completely functional application, except that when examining the process ID's of the various functions/slots being called, I can see that (in particular) the slot handlyMySignal() is being executed in the same thread as both the WindowApp constructor and other (not shown here) slots in MyObject.
That's correct, as you moved only "myObject" to the new thread and not "otherObject". You should move "otherObject" to the new thread if you want its slots to be executed in context of this thread. "myObject" doesn't have to be moved in this case.

bobrien
13th September 2011, 18:05
Thank you for your quick response. However, as I was concerned that I would, I managed to improperly simplify my code and not show the real problem. Let's try this again, with actual class names that make a bit more sense.

There are three main classes involved here: CameraThread, Controller, and NozzleApp.

The idea behind CameraThread is to keep all of the low-level interaction of the camera out of the main UI class. What it *SHOULD* be doing is every time a timer goes off, expose and transfer a new image. All I want it to do, ultimately, is emit a signal that contains a new image (an OpenCV Mat in this case), and perform all of its tasks in a separate thread. This is seen below:


class CameraThread : public QObject
{
Q_OBJECT

public:
CameraThread();

private:
Camera* camera;
QTimer imageTimer;

private slots:
void getNewImage();

signals:
void newImage(Mat);
};

CameraThread::CameraThread()
{
camera = new Camera();
QObject::connect(&imageTimer, SIGNAL(timeout()), this, SLOT(getNewImage()));
}

void CameraThread::getNewImage()
{
Mat image_out;
camera->captureSingleImage();
image_out = camera->lastImageColor();
emit newImage(image_out);
}


The Controller class is the interface to some heavy-duty image processing routines. It has its own threaded "pipeline" of processing steps, which works great in its own thread (it was created by sub-classing QThread and reimplementing run()). In this case, its only job is to receive any new images, save some state/data about them, and then pass them off to this processing pipeline.



class Controller : public QObject
{
Q_OBJECT

public:
Controller(NozzleApp* parent);

private:
NozzleApp* myParent;

private slots:
void handleNewImage(Mat newImage);

signals:
void new_input_image(Mat);
};

Controller::Controller(NozzleApp *parent) : myParent(parent)
{

}
void Controller::handleNewImage(Mat newImage)
{
// save some state first
emit new_input_image(newImage);
}


Finally, NozzleApp is the high-level GUI class. It creates most of the major objects, and sets up some of the communication between objects. Note that the Controller gets passed its parent object (NozzleApp), but CameraThread does not.



class NozzleApp : public QMainWindow
{
Q_OBJECT

public:
NozzleApp();

private:
Controller* controller;
CameraThread* cam_thread;
QThread camera_thread;
};

NozzleApp::NozzleApp()
{
setupUi(this);
controller = new Controller(this);
cam_thread = new CameraThread();
QObject::connect(cam_thread, SIGNAL(newImage(Mat)), controller, SLOT(handleNewImage(Mat)));

camera_thread.start();
cam_thread->moveToThread(&camera_thread);
}


So, back to the original question: What I would hope and expect to see is that the slot getNewImage() in CameraThread (which does the time expensive camera opterations) is executed in a different thread then handleNewImage() in Controller. This is not happening - why?

Based on everything I've read, and based on your prior response, those two slots are NOT in the same context. I realize that I neglected the QTimer of kicking off getNewImage(), but I figured since the QTimer was supposedly in my new thread, well then its slot would also execute in the new thread (which is not the case).

Thanks again,
Barry

amleto
13th September 2011, 21:26
slots get handled in the thread that the object exists in (by default). I forget how much you can change the behaviour by changing connect(...) arguments.

It's conceivable that moving the connect for cam_thread after the point where cam_thread has moved thread will change the behaviour of the connection

high_flyer
14th September 2011, 10:29
As a side note:
your naming convention is confusing, and specially in such cases of this sort, can lead to the problem you are having.
Your CameraThread object is not a thread, so don't call it "thread".
And having a cam_thread and CameraThread is confusing as well, specially when only one of these objects is a thread.

Now,

but I figured since the QTimer was supposedly in my new thread,
I think this is where the problem is.
The docs say (for moveToThread()):

Changes the thread affinity for this object and its children.
Your QTimer is a member, but no a child.
Cerate it on the heap with CameraThread as parent, and see if it changes things.