3

スレッド間で QTcpSocket と通信できないことは承知していますが、何が間違っているのかわかりません。

他のスレッドにあるQTcpSocketにデータ送信信号を送信するとすぐに、QSocketNotifierはそれ自体を無効にして、QTcpSocketがデータを読み取れるようにする必要があります。上記のエラーが発生するため、無効にならず、readyRead() シグナルが発行されません。

QEventLoop を使用して、メイン ループをロックせずにブロック メカニズム (同期通信 - ModBus プロトコル) を実装しています。過去にその関数に問題があったため (タイムアウト)、waitReadyRead() を使用できません。

https://bugreports.qt-project.org/browse/QTBUG-24451

https://bugreports.qt-project.org/browse/QTBUG-14975

なぜ私がこれほど多くのスレッドをセットアップするのか、興味がある人もいるでしょう。

Thread1: 新しい通信ごとに新しいスレッドが作成され、それを「グローバル」ミューテックスでロックして、他の関数が並列通信を実行しないようにします。そのスレッド内 - 準備とミューテックスのロックの後、Thread2 にある Qt::QueuedConnection シグナル (デバイスとの常時通信) によって QTcpSocket と通信します。

Thread2: QTcpSocket オブジェクトが作成され、スレッドに移動されました。デバイスに接続したら、ソケットを閉じたくありません。メイン イベント ループをブロックしているため、QTcpSocket からのシグナルを実行するには他のイベント ループが必要です。

そしてコード:

void someFunctionThatWantToSendData( void ) // Main Thread
{
    ElfKernel::KernelSingleton::Instance().getGUIcontrol()->getCtrlUnitPtr()->execModbusCommand( someParameters, someCommunicationFunction );
}

ElfKernel::FrameReceived ControlUnit::execModbusCommand( std::list<unsigned int> paramsToCommand, FunctionStringList execCommand ) // Main Thread
{
    ModbusCommandReply* mcr = new ModbusCommandReply(); // THREAD_1!!!!!! this object run in a Thread -> ModbusCommandReply : public QThread
    auto timeout = ElfKernel::CommunicationManagerSingleton::Instance().getTimeout();
    mcr->execute( paramsToCommand, execCommand );  // executes THREAD_1
    bool hasFinishedWithoutTimeout = mcr->wait(timeout);  // waiting for THREAD_1 to finish
    FrameReceived ret = mcr->getData();
    delete mcr;
    return ret;
}

// this method is executed after preparations done by: mcr->execute( paramsToCommand, execCommand );
ElfKernel::FrameReceived CommunicationManager::getData( std::string data )  // THREAD_1
{
    emit signalTcpSocketWriteData( data );
    loop.exec();    // QEventLoop declared in a header; program loops until QTcpSocket::readyRead() signal will hit the slot TcpSocketClient::readyRead()
    FrameReceived fr = tcpSocket->readDataString();
}

// executed by: emit signalTcpSocketWriteData( data );
void TcpSocketClient::writeDataSlot( std::string data ) // THREAD_2!!!!!
{
    tcpSocket_->write( data );
    tcpSocket_->flush();
}

void TcpSocketClient::readyRead( void ) // should be executed in THREAD_2 but signal readyRead() never arrives here because a get error on Output: "QSocketNotifier: socket notifiers cannot be disabled from another thread"
{
    ElfKernel::CommunicationManagerSingleton::Instance().loop.quit();   // breaks loop.exec() in CommunicationManager::getData()
}

// constructor
CommunicationManager::CommunicationManager( void )  // Main Thread
{
    tcpSocket = new TcpSocketClient();
    tcpSocketThread = new QThread();
    tcpSocket->moveToThread( tcpSocketThread );
    QObject::connect( tcpSocketThread, SIGNAL(started()), tcpSocket, SLOT(prepare()) );
    tcpSocketThread->start();   // CREATED THREAD_2!!!!!

    QObject::connect(this, SIGNAL(signalTcpSocketWriteData(std::string)), tcpSocket, SLOT(writeDataSlot(std::string)), Qt::QueuedConnection );
    QObject::connect(this, SIGNAL(signalTcpSocketReadData(std::string *)), tcpSocket, SLOT(readDataSlot(std::string *)), Qt::QueuedConnection);

    // and other stuff
}

// constructor
TcpSocketClient::TcpSocketClient(QObject *parent)   // Main Thread - before it is moved to THREAD_2
{
    parent;

    //prepare();    // it is executed after signal QThread::started() is emitted
}

// method run after thread started
void TcpSocketClient::prepare( void )   // THREAD_2
{
    tcpSocket_ = new QTcpSocket(this);
    QObject::connect(tcpSocket_, SIGNAL(disconnected()), this, SLOT(hostHaveDisconnected()));
    QObject::connect(tcpSocket_, SIGNAL(readyRead()), this, SLOT(readyRead()));
}

だから私が言ったように、スロット TcpSocketClient::readyRead() は呼び出されず、出力にメッセージが表示されます:「QSocketNotifier: ソケット通知を別のスレッドから無効にすることはできません」。

このコードを置き換えると:

ElfKernel::FrameReceived ControlUnit::execModbusCommand( std::list<unsigned int> paramsToCommand, FunctionStringList execCommand ) // Main Thread
{
    ModbusCommandReply* mcr = new ModbusCommandReply(); // THREAD_1!!!!!! this object run in a Thread -> ModbusCommandReply : public QThread
    auto timeout = ElfKernel::CommunicationManagerSingleton::Instance().getTimeout();
    mcr->execute( paramsToCommand, execCommand );  // executes THREAD_1
    bool hasFinishedWithoutTimeout = mcr->wait(timeout);  // waiting for THREAD_1 to finish
    FrameReceived ret = mcr->getData();
    delete mcr;
    return ret;
}

と:

ElfKernel::FrameReceived ControlUnit::execModbusCommand( std::list<unsigned int> paramsToCommand, FunctionStringList execCommand ) // Main Thread
{
    FrameReceived ret = execCommand( paramsToCommand );  // Main Thread
    return ret;
}

そのため、プログラムはスレッドにプッシュされず、メイン スレッドで実行されます (違いのみです!)。その後、すべてが正常に機能します。readyRead スロットはループ ブレークで機能し、データを取得します。mcr->execute 内に mutex.lock() を設定しているため、ModbusCommandReply をスレッドとして実行する必要があります。そのため、プログラムの他の部分でメソッド execModbusCommand を実行するたびに、他のスレッドがリソースを解放して通信を確立するまで停止します。

「QSocketNotifier: ソケット通知機能を別のスレッドから無効にすることはできません」と表示される理由がわかりません - 新しいオブジェクトをヒープに配置してスレッドが開始された後、THREAD_2 内に QTcpSocket を設定しました: tcpSocket_ = new QTcpSocket(this);

どんな助けにも感謝します、ありがとう!

4

1 に答える 1

5

メインスレッドで接続しました:

tcpSocket->connect( ... );

tcpSocketThread ではなく、メイン スレッドで子オブジェクトを生成します。

また、次のことをお勧めします。

tpcSocket->setParent(0);
// before
tcpSocket->moveToThread( tcpSocketThread );
于 2013-05-17T13:46:03.160 に答える