Qtで、マルチスレッドでソケットプログラミングをしていたら、次のようなエラーがでた。解決方法をメモっておく。
QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread
今回は、次のようにDialog.cppのon_startPUshButton_clicked()にて、QTcpSocketのソケット(tcpSocket)を作りconnectToHostでサーバに接続した後、startJoyThreadでスレッドを開始している。その際に、tcpSocketをアドレス渡している。
void Dialog::on_startPushButton_clicked() { //---------------------- // Socket QTcpSocket *tcpSocket = new QTcpSocket; tcpSocket->connectToHost(ipAddress,tcpPort); tcpSocket->waitForConnected(); //---------------------- //---------------------- // Thread // stop thread if(myJoyThread.isRunning()) myJoyThread.stop(); // start myJoyThread.startJoyThread(tcpSocket); //---------------------- qDebug("%d",ui->joyComboBox->currentIndex()); }
スレッド側では、run()にてDialogから受け取ったソケット(tcpSocket)に、writeでメッセージを送っている。
void JoyThread::startJoyThread(QTcpSocket *tcpSocket) { qDebug()<<"startJoyThread: "<<currentThreadId(); this->tcpSocket = tcpSocket; // stop thread if(isRunning()) stop(); // start thread start(); runFlag = true; } void JoyThread::run() { qDebug()<<"run: "<<currentThreadId(); while(runFlag) // loop if runFlag on { // make command QByteArray sendMsg = QString("%1:%2:%3").arg(cmd).arg(id).arg(val).toUtf8(); // send command to server through socket "tcpSocket" tcpSocket->write(sendMsg); } }
実行結果は以下のようになる。「startJoyThread: 0x1e44」と「run: 0x5c0」は、currentThreadId()の出力でスレッドIDである。
startJoyThread: 0x1e44 run: 0x5c0 QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread
このstartJoyThreadとrunのスレッドIDが0x1e44と0x5c0と異なることからわかるように、Dialog::on_startPushButton_clicked()とJoyThread::run()は異なるスレッドで動いている。
具体的には、下記に説明されているように、QtのGUI(すべてのwidgetと関連するクラス)はメインスレッド(またはGUIスレッド)で動いていて、JoyThread::run()のように新たに生成されたスレッドは、メインとは別のワーカースレッドで動いている。つまり、上記の場合、一つのソケット(tcpSocket)に異なる2つのスレッドからアクセスしているため、「QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread」
というエラーがでていたのだ。
This thread is called the "main thread" (also known as the "GUI thread" in Qt applications). The Qt GUI must run in this thread. All widgets and several related classes, for example QPixmap, don't work in secondary threads. A secondary thread is commonly referred to as a "worker thread" because it is used to offload processing work from the main thread.
http://doc.qt.io/qt-4.8/thread-basics.html
これを解決するためには、「moveToThread」を用いて、ソケットの所有者をメインスレッドから、実際に通信を行うワーカースレッドmyJoyThread(JoyThread)に変えてやればよい。
void Dialog::on_joyStartPushButton_clicked() { //---------------------- // Socket tcpSocket = new QTcpSocket; tcpSocket->connectToHost(ipAddress,tcpPort); tcpSocket->waitForConnected(); <span style="color: #ff5252">tcpSocket->moveToThread(&myJoyThread); //tcpSocket in main thread is moved to JoyThread (could be accessed from run())</span> //---------------------- //---------------------- // Thread // stop joystick thread if(myJoyThread.isRunning()) myJoyThread.stop(); // start joystick thread with joystick id myJoyThread.startJoyThread(ui->joyComboBox->currentIndex(),tcpSocket); //---------------------- }