PDA

View Full Version : signals/slots across threads



high_flyer
12th March 2007, 22:26
Hi,

Qt4.2.2 on linux.
I have implemented a threaded communication scheme, but the slots of a non gui thread class are executed in the GUI thread.
I was hoping maybe some one here could point to my mistake.
Here is what I did in meta code, after the code a text explaining the behavior:


//Base socket thread class
class SocketThread : public QThread
{
Q_OBJECT
protected:
QTcpSocket *m_socket;
QString m_host;
quint32 m_port;
QMutex m_socketMutex;
QDataStream *m_dataStream;
public:
SocketThread(QString host,quint32 port,QObject *parent = 0);

~SocketThread();
const QDataStream & stream();
void run();
public slots:
virtual void readSocket();
};

void SocketThread::run()
{
if(!m_socket)
{
m_socket = new QTcpSocket();
if(m_socket) m_dataStream = new QDataStream(m_socket);
connect(m_socket,SIGNAL(readyRead()),this,SLOT(rea dSocket()));
if(m_socket) m_socket->connectToHost(m_host,m_port);
}
exec();
}

//Actual working class
class HPSCSocket : public SocketThread
{
Q_OBJECT
public:
HPSCSocket(QString host,quint32 port,QObject *parent = 0);

~HPSCSocket();
signals:
void sig_hpscReadout(int,float,float);
public slots:
void sendHPSCCommand(HPSC_Command cmd,long usWait);
void readSocket();

};

void HPSCSocket::sendHPSCCommand(HPSC_Command cmd,long usWait=0)
{
qDebug()<<"HPSCSocket::sendHPSCCommand";
if(m_dataStream)
{
qDebug()<<"sending command";
*m_dataStream<<cmd;
m_socket->flush();
qDebug()<<"about to sleep "<<usWait;
usleep(usWait);
qDebug()<<"woke up";
}
}

//the class (GUI) that communicates through the threaded socket class HPSCSocket
FrmPower::FrmPower(BUS bus1,BUS bus2,QWidget *parent)
: QWidget(parent)
{
m_hpscSocket = new HPSCSocket(m_hpscHost.toString(),m_hpscPort,this);
connect(this,SIGNAL(sig_hpscCommand(HPSC_Command,l ong)),m_hpscSocket,SLOT(sendHPSCCommand(HPSC_Comma nd,long)),Qt::QueuedConnection);
connect(m_hpscSocket,SIGNAL(sig_hpscReadout(int,fl oat,float)),this,SLOT(hpscReadout(int,float,float) ));
}

void FrmPower::bus1()
{
command.cmd = SETCURR1;
command.var = m_src1a;
emit sig_hpscCommand(command,120000);
}


As you can see, a socket is created in its own thread (not the GUI thead) in SocketThread::run().
The GUI class FrmPower has a member variable of type HPSCSocket which is a subclass of the SocketThread class.
The aim of this scheme, is to allow FrmPower to send information through the threaded socket as can be seen in FrmPower::bus1().
(The reason is, that after each socket transmition there is a need for various sleep intervals, which would freeze the GUI thread, and other problems as well)
The problem is, that although the socket is created in seperate thread to the GUI thread, the execution of HPSCSocket::sendHPSCCommand() happens in the main GUI thread, and I have no idea why.
In addition, the output:

QSocketNotifier: socket notifiers cannot be enabled from another thread

comes out in HPSCSocket::sendHPSCCommand on the socket operations, but code runs, just not in the right thread.
If any one could point me to my mistake it will be very appreciated.
I didn't insert code that is not relevant for illustrating the problem, such as proper constructors , mutextes etc.

Thanks in advance!

jacek
12th March 2007, 22:45
This has been discussed several times. QThread object lives in the thread that created it, but objects created in run() live in a new thread. You have to add Qt::DirectConnection to your connect statements.


connect( m_socket, SIGNAL(readyRead()), this, SLOT(readSocket()), Qt::DirectConnection );

high_flyer
12th March 2007, 22:50
Hi Jacek,

thanks for the fast reply.

QThread object lives in the thread that created it, but objects created in run() live in a new thread. You have to add Qt::DirectConnection to your connect statements.
I know, and I did(or at least its was what I think I did :) ), which is why I don't understand why the slot is executed in the GUI thread.
My SocketThread derived class variable is created in a run() method, so it lives in its own thread right?
So why his slots are executed in the main thread?

high_flyer
12th March 2007, 22:53
I am not interested in DirectConnection - that is the whole idea of having the thread for.
I want to post the signals, and let the target thread execute them in its own time.

wysota
12th March 2007, 23:14
Hi Dani, nice to hear from you again :) I hope we can drink a 3EUR worth small bottle of water some time soon :)

About the problem - as Jacek pointed out the problem of thread affinity causes Qt to misinterpret your intentions. HSCSocket (thread) is created with the GUI thread affinity and you then make a connection to the thread object that is handled by the GUI thread.
There are two solutions:
1. make the connection not to the thread object, but to an object created by the thread's run method
or
2. push the thread object to the thread itself, like so:

connect(m_hpscSocket, SIGNAL(started()), this, SLOT(onStarted()));
m_hpscSocket->start();
//...
void FrmPower::onStarted(){
m_hpscSocket->moveToThread(m_hpscSocket);
}
That just might work :)

Remember you'll lose the parent-child relation between FrmPower object and the thread object.

jacek
12th March 2007, 23:18
My SocketThread derived class variable is created in a run() method, so it lives in its own thread right?
Yes, but "this" (i.e. SocketThread) lives in the GUI thread, because it was created there.


So why his slots are executed in the main thread?
Because when you connect objects living in different threads (m_socket and "this") Qt by default creates a queued connection. Since "this" lives in the GUI thread, all meta call events will be queued by the GUI thread.


I want to post the signals, and let the target thread execute them in its own time.
In that case you need two objects: a socket and a handler.

void SocketThread::run()
{
if(!m_socket)
{
m_socket = new QTcpSocket();
m_handler = new Handler( m_socket ); // <-- this object is responsible for reading data from the socket
connect(m_socket,SIGNAL(readyRead()),handler,SLOT( readSocket()), Qt::QueuedConnection);
...
}
exec();
}In this case all meta call events will be queued by the SocketThread, but it really doesn't change much. You will still have one thread that reads data from the socket --- it will just do it in a bit different way.

If you want to have two threads, you need one that will contain the socket and will be responsible for appending the received data to a buffer (queue?) and another one that will analyze that data and remove it from the buffer.

high_flyer
12th March 2007, 23:34
Hi Wysota and Jacek,

Yes, it will be cool to have a 3EU drink again, I wont mind if it will be a trolltech event though ;)
Ok, I think I understand now.
Hmm, I new I need to implement a queue, and I thought that if I use the thread safe signal/slots I will just let the QThread queue do the work, and all I need to do is post signals from my gui thread.
That was what I was trying to do anyhow...
But you are right, my SocketThread class it self lives in the GUI thread.
I guess I will just fall back to custom events then.
I guess that the threaded signals/slots are doing just that behind the scenes, but for me it will be less coding with custom events.

Thanks again for the first class help guys!

wysota
12th March 2007, 23:45
If you want a queue, maybe it's better to use postEvent() directly? Signals/slots won't give you any advantage here...

high_flyer
12th March 2007, 23:49
Yes, its exactly what I meant when I said custom events.
Thanks.

wysota
12th March 2007, 23:56
Yes, I know :) I just noticed that after sending my post and added the second sentence just to make the post say something anyway ;)