Im знает, что я не могу общаться с QTcpSocket между потоками, но я не могу найти то, что Im делает неправильно.QSocketNotifier: уведомления сокетов не могут быть отключены из другого потока
Как только я посылаю сигнал передачи данных в QTcpSocket, который находится в другом потоке, QSocketNotifier должен отключить себя, чтобы позволить QTcpSocket считывать данные. Поскольку я получаю выше ошибки, он не будет отключен, и сигнал readyRead() не будет выпущен.
Im использует QEventLoop для реализации механизма блокировки (синхронная связь - протокол ModBus) без блокировки основного контура. Я не могу использовать waitReadyRead(), потому что у меня были проблемы с этой функцией в прошлом (таймауты):
https://bugreports.qt-project.org/browse/QTBUG-24451
и
https://bugreports.qt-project.org/browse/QTBUG-14975
Кто-то может быть интересно, почему Im создание так много потоков :
Thread1: Каждое новое сообщение создает новый поток и блокирует его с помощью «глобального» мьютекса, чтобы гарантировать, что никакая другая функция не будет выполнять параллельную связь. Внутри этой нити - после подготовки и блокировки мьютекса я общаюсь с QTcpSocket по сигналу Qt :: QueuedConnection, который находится в Thread2 (постоянная связь с устройством).
Thread2: Объект QTcpSocket создан и перемещен в поток. Как только я подключаюсь к устройству, я не хочу закрывать сокет. Поскольку Im блокирует цикл основного события, мне нужен другой цикл событий для запуска сигналов из 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 слот работает с разрывами цикла, и я получаю данные. Мне нужно запустить ModbusCommandReply как поток, потому что я установил mutex.lock() внутри mcr-> execute, поэтому всякий раз, когда другая часть моего метода запуска программы execModbusCommand будет остановлена, пока другой поток не освободит ресурсы для установления связи.
Я не понимаю, почему он говорит: «QSocketNotifier: сокет-оповещения не могут быть отключены из другого потока» - я установил QTcpSocket внутри THREAD_2 после того, как поток начал, поместив новый объект в кучу: tcpSocket_ = new QTcpSocket (this);
Я буду благодарен за любую помощь, спасибо!