Synchronously waiting for a list of signals
-
Hi, trying something like this:
https://falsinsoft.blogspot.com/2014/02/qt-wait-for-signal-in-synchronously-mode.html
I need to wait synchronously for a list for signals. This is what I tried:SignalWait::SignalWait(QObject *object, QList<const char*> &signalList) { for(int i = 0; i < signalList.size(); ++i) { m_signalSpies.append(new QSignalSpy(object, signalList.at(i))); } } SignalWait::~SignalWait() { while(!m_signalSpies.isEmpty()) { delete m_signalSpies.takeFirst(); } } QStringList SignalWait::wait(const int timeoutSec) { QTime timer(0, 0, timeoutSec); timer.start(); QStringList receivedSignals; // mydebug(9, QString("Waiting for %1 signals").arg(m_signalSpies.size())); QList<QSignalSpy*>::const_iterator spyIter = m_signalSpies.begin(); while((*spyIter)->isEmpty()) { if(timer.elapsed() > timeoutSec * 1000) { mydebug(5, QString("timed out after %1s without signal").arg(timeoutSec)); return QStringList(); } QCoreApplication::processEvents(); if(++spyIter == m_signalSpies.end()) { spyIter = m_signalSpies.begin(); } } for(spyIter = m_signalSpies.constBegin(); spyIter != m_signalSpies.constEnd(); ++spyIter) { if(!(*spyIter)->isEmpty()) { receivedSignals.append((*spyIter)->signal()); mydebug(9, QString("Got signal %1").arg(QString((*spyIter)->signal()))); } } return receivedSignals; } ///////////////////////////////////////////////////// foreach(QString inFile, inFiles) { // # Step 1: Pending onFilePending(inFile); m_response->sendFile(pendingDir.canonicalPath() + "/" + inFile); SignalWait finishWaiter(m_response, QList<const char*>() << SIGNAL(fileError(const QString&)) << SIGNAL(fileDone(const QString&))); // # Step 2: Done or Error QStringList receivedSignals = finishWaiter.wait(m_timeOutSec); foreach(QString receivedSignal, receivedSignals) { if(receivedSignal.startsWith("fileDone")) { onFileDone(inFile); } else if(receivedSignal.startsWith("fileError")) { onFileError(inFile); } else { myerror(QString("Waited for signal \"done\" or \"error\" on file %1, but received signal %2") .arg(inFile).arg(receivedSignal)); } if(receivedSignal.isEmpty()) { myerror(QString("Waited for signal \"done\" or \"error\" on file %1, timed out after %2 seconds") .arg(inFile).arg(m_timeOutSec)); } } sleep(g_confDelay); }
But I always got "timed out after ..s without signal" eventhough I emitted the signal.
Could that code work like this? -
unsigned int signalCount = 0; QEventLoop waitLoop; const auto signalReceived = [&signalCount,&waitLoop]()->void{ if(--signalCount==0) waitLoop.quit(); } connect(sender1, &Sender1::signal1,signalReceived); ++signalCount; connect(sender2, &Sender2::signal2,signalReceived); ++signalCount; // ... connect(senderN, &SenderN::signalN,signalReceived); ++signalCount; waitLoop.exec(); qDebug("All signals received");
-
@mssm said in Synchronously waiting for a list of signals:
But I always got "timed out after ..s without signal" eventhough I emitted the signal.
Could that code work like this?No, not really. While you are in a while loop, no other code is running. So no other code is emitting a signal, and you aren't yielding back into the event loop for signal dispatch to happen. So after you while loop finishes, the app goes back to the event loop, then runs some code that will generate a signal. At least not without multiple threads.
But what are you actually trying to accomplish? Spinning in a busy loop waiting for a signal looks like the wrong approach. The whole point of a signal is that you can just have the code that runs on a signal get triggered in a slot -- you don't have to sit around waiting for it yourself. Signals and busy waiting is a strange combination and definitely a code smell.
-
@wrosecrans said in Synchronously waiting for a list of signals:
While you are in a while loop, no other code is running. So no other code is emitting a signal, and you aren't yielding back into the event loop for signal dispatch to happen.
And the QCoreApplication::processEvents() in the while loop doesn't solve that?
Is the blogspot approach I referenced a bad idea in general?I know this does not conform with the basic idea of signals and slots. I tried to create a synchronous state machine. sendFile() can transmit a file via different ways, like QNetworkRequest. The result if this was successful cannot be returned directly from sendFile() directly as there are 2 signals fileError() and fileDone() emitted by different slots like onFinished() or onSslErrors().
While processing a list of files I need to wait for exactly one of fileError() or fileDone() before continuing with the next file. Maybe I better replace the loop over list of files with an asynchronous signal slot, and continuing in some onError() and onDone() slot. Additionally I need a global timer for timeout, acting like an error after timeout.
-
@mssm said in Synchronously waiting for a list of signals:
While processing a list of files I need to wait for exactly one of fileError() or fileDone() before continuing with the next file. Maybe I better replace the loop over list of files with an asynchronous signal slot, and continuing in some onError() and onDone() slot. Additionally I need a global timer for timeout, acting like an error after timeout.
Yeah, that sounds like a good approach and should not be too hard to implement.
If you really want a blocking approach, you should do it in a separate thread.
Regards
-
I always come back to the same issue I had in the beginning. I have a global list of pending files to process:
while(!m_currentPendingFiles.isEmpty()) { processFile(m_currentPendingFiles.takeFirst());
starts the non-blocking processFile() for all files in parallel, but should actually wait for finished signal before going to the next file.
if(m_currentPendingFiles.isEmpty()) { myerror("No file to process"); } else { processFile(m_currentPendingFiles.takeFirst()); }
It's a command line process, that should process all files and quit. This would only send one file, but doesn't process the fileDone() signal anymore, just quits.
-
Hi @mssm said in Synchronously waiting for a list of signals:
if(m_currentPendingFiles.isEmpty()) {
myerror("No file to process");
} else {
processFile(m_currentPendingFiles.takeFirst());
}That already looks good. This piece of code should be executed
- one time at the beginning
- every time a file has been completely processed
and you should be done.
-
Simplified workflow:
class FileProcessor : public QObject { Q_OBJECT public: void process(); private: Worker* m_worker; QTimer m_fileTimer; QStringList m_currentPendingFiles; void processFile(const QString&); private slots: void onFileDone(); void onFileError(); void onFileTimeout(); }; void FileProcessor::process() { // populating m_currentPendingFiles... processFileList(); } void FileProcessor::processFileList() { if(m_currentPendingFiles.isEmpty()) { myerror("No file to process"); } else { processFile(m_currentPendingFiles.takeFirst()); } } void FileProcessor::processFile(const QString &fileName) { m_worker->sendFile(m_currentFile); connect(m_worker, SIGNAL(fileError()), this, SLOT(onFileError())); connect(m_worker, SIGNAL(fileDone()), this, SLOT(onFileDone())); connect(&m_fileTimer, SIGNAL(timeout()), this, SLOT(onFileTimeout())); m_fileTimer.start(45000); // 45 seconds } void FileProcessor::onFileDone() { myinfo("File done"); resetFile(); } void FileProcessor::onFileError() { myinfo("File error"); resetFile(); } void FileProcessor::onFileTimeout() { myerror("File transmission timed out"); onFileError(); } void FileProcessor::resetFile() { m_currentFile.clear(); m_worker->disconnect(); processFileList(); }
sendFile() is blocking, so the function works fine until finished. The worker actually sends some error or done signal after that which both result in a slot doing another processFileList() until the list is empty, but it doesn't call the slot anymore. I still think I need to wait for the signal, or process the main loop?
-
Ok, to give you more information about the blocking sendFile(), maybe the mistake is there. It is currently blocking as it starts a QProcess and blocking:
Worker::Worker() { connect(&m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(onError(QProcess::ProcessError))); connect(&m_process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(onFinished(int,QProcess::ExitStatus))); connect(&m_process, SIGNAL(readyReadStandardError()), this, SLOT(onReadyReadStandardError())); connect(&m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(onReadyReadStandardOutput())); connect(&m_process, SIGNAL(started()), this, SLOT(onStarted())); } Worker::sendFile() { //... m_process.start(command); if(m_process.waitForStarted(3000) == true) { m_process.write(contentsStr.toUtf8()); m_process.closeWriteChannel(); if(m_process.waitForFinished(40000)) { mydebug("Process finished"); } else { mydebug("Process has not finished after timeout"); emit fileError(); // TODO: Kill process } } else { emit fileError(); } //... }
Maybe I should not block for QProcess started and finished.