QUdpSocket and QThread
-
Hey guys!
I have a problem with a QUdpSocket running in a different thread.
Here's the source code:
@
/*- main.cpp
- Main app
- (actually the code is in a constructor of a class, but I've shortened it a bit...)
- That's where the RemoteConnection object is created and the thread is started.
*/
int main()
{
remoteConnection = new RemoteConnection();
remoteConnection->start();
remoteConnection->setHostPort(1234); // ERROR!
}/*
- remoteconnection.h
- Class RemoteConnection
*/
class RemoteConnection : public QThread
{
Q_OBJECTprivate: QUdpSocket *udpSocket;
public: RemoteConnection(ve::Scene *scene);
public: void setHostPort(unsigned int hostPort);
public slots: void readyRead();
public: virtual void run();
}/*
- remoteconnection.cpp
- Class RemoteConnection implementation
*/
RemoteConnection::RemoteConnection()
{
this->remoteIP = "127.0.0.1";
this->remotePort = 5000;
this->hostPort = 5001;
udpSocket = NULL;
}void RemoteConnection::run()
{
// create the socket
udpSocket = new QUdpSocket;connect(udpSocket, SIGNAL(readyRead()) ,
this, SLOT(readyRead()),
Qt::DirectConnection);// bind the socket
this->bind();// execute
exec();
}RemoteConnection::bind()
{
// host port may have changed, so close current port and open new port
udpSocket->close();
udpSocket->bind( this->getHostPort() );
}void RemoteConnection::setHostPort(unsigned int hostPort)
{
this->hostPort = hostPort;
bind();
}
@In the main loop I create my RemoteConnection object and tell it to start a new thread. In RemoteConnection's run() method I create a QUdpSocket object and connect it's readyRead() signal to RemoteConnection's readyRead Slot. After that I call the bind() method of RemoteConnection to bind the socket to the specified port.
During the execution of the programm it might happen that the user changes the port the socket listens on. In that case, RemoteConnection::setHostPort() is called from the main-thread and I have to rebind the socket object. But that causes the following error during execution of the programm:
ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to objects owned by a different thread. Current thread 2d8ea00. Receiver...''
I understand that my remoteConnection object and the udpSocket object run in different threads, because I created the socket object in RemoteConnection's run() method.
But is there a way to manipulate the socket object from a different thread?
Or do I have to create a wrapper class for QUdpSocket and assign a signal like "hostPortChanged()"?I know maybe it's not the best solution to create the udpSocket object in a different thread. But unfortunately I have no proper access to the main app and can't change the whole architecture of the programm.
Any ideas?
Thanks a lot for your help!
-
Did you read "Threads, Events and QObjects":http://developer.qt.nokia.com/wiki/Threads_Events_QObjects from the wiki already?
-
Hello Volker,
thanks for the advice! Yes, I've already read it. Unfortunately neither of the provided solutions there seems to work for me.I tried
@QMetaObject::invokeMethod(udpSocket, "bind", Q_ARG(unsigned int, this->hostPort))@
at the RemoteConnection's bind() method but it returned false, because the QUdpSocket's bind() method is not a slot. I understand that there is a way to move objects from one thread to another, but I didn't quite get the concept behind it an where (in the code) to move the object.
Anyone done something similar?
Thanks a lot for your help!
Greets,Mr.Orange
-
Lets start at the beginning here. Why do you think you need a new thread for your socket in the first place?
-
Hey Andre,
thanks for the reply. I don't know if I actually need a new thread for the socket. The piece of software I'm currently working on was created by someone else and has grown over the years to a very complex system. The problem with the port binding is a bug nobody discovered before, because it used to be very unlikely to change the host port during runtime. But now I need this functionality, so I gotta fix this bug :).
Thanks for your help!
Greets,
Mr.Orange -
Well, the core of your problem is actually described in the article Volker linked to. Here is what you are doing:
You are creating an object of class RemoteConnection in thread 1. When you start the thread that RemoteConnection manages (yes, QThread manages a thread, which is not the same as being a thread!), the code in RemoteConnection::run() is executed in the context of the newly created thread 2. So, your udpSocket is created in thread 2. Note that your RemoteConnection instance itself is still (and should remain in!) thread 1.
Now, you try to change the port binding. You do that by calling RemoteConnection::setHostPort(), which in turn calls RemoteConnection::bind(). These calls will probably be executed in the context of thread 1. However, you are manipulating an object that lives in thread 2. Without savety precautions, that is unsave!
Note that also the direct connection between the UDP socket's signal and the RemoteConnection readyRead slot is unsafe.
You have several potential ways to solve this:
get rid of the thread. It is not needed, unless you also do extensive processing on incomming or outgoing data within the thread itself, or
Make RemoteConnection inherit QObject (not inherited from QThread), and instead do something like this:
@
RemoteConnection* connection = new RemoteConnection(0);
QThread* socketThread = new QThread(this);
connection->moveToThread(socketThread);
socketThread->start();
@Then, make the methods in your RemoteConnection object slots, and only call them using queued (or automatic) connections.
You could even do something like this for your methods in RemoteConnection:
@
class RemoteConnection:public QObject
{
//...
public slots:
void setHostPort(unsigned int hostPort);private slots:
void setHostPortImplementation(unsigned int hostPort);
}//in implementation:
void RemoteConnection::setHostPort(unsigned int hostPort)
{
if (QThread::currentThread() == thread()) {
//ok, this method is called from the same thread we live in so do direct method call
setHostPortImplementation(hostPort);
} else {
//method is called from outside our own thread, do save invocation instead
QMetaObject::invokeMethod(this, "setHostPortImplementation", Q_ARG(unsigned int, hostPort), Qt::QueuedConnection);
}
}
@If you protect your methods this way, you can be sure that they are always savely called.
The preferred method, IMHO, is to get rid of unneeded threads so you can avoid all this hassle.
-
Andre,
thank you so much for your detailed reply! I think I'll try to get rid of the threads somehow. Then do some excessive testing and hope that everything works fine.
I probably won't finish today, but after the weekend I'll give you a short feedback on my progress.Thanks again for your help & have a nice weekend!
Greets,
Mr.Orange