QtConcurrent::mapped with a member function with QEventLoop
-
Hi,
[OS Windows, Qt6.6.1, IDE. Qt Creator. ]
I am writing some code which finds all media files (images & videos) in a folder and than reads and returns all meta data. Meta data is than used for numerous purposes, one being to be able to sort files by creation date (= when the media was shot, not when the file was dumped on hard drive of the PC or external HD) ascending or descending.I use QtConcurrent:mapped for multithreading as there is a large number of media files.
My code (the relevant part for this issue starts with declaration of
QMediaPlayer player;below://fileListIndex is in place. auto sequence = QVector<int>::fromList(fileListIndex); auto future = QtConcurrent::mapped(sequence, std::bind(&MappedProcessor::getMetaData, this, std::placeholders::_1)); m_futureWatcherLoadMetaData->setFuture(future);MetaDataInfo MappedProcessor::getMetaData(int index) { MetaDataInfo info; info.fileIndex = index; QString file = model->loadDirModel.files.at(index); QFileInfo f(file); info.fileSize = f.size(); info.mediaFileType = (isImageFile(file)? image : video); QMediaPlayer player; player.setSource(QUrl::fromLocalFile(file)); QTimer timer; timer.setSingleShot(true); QEventLoop loop; connect(&player, &QMediaPlayer::mediaStatusChanged, &loop,&QEventLoop::quit); connect( &timer, &QTimer::timeout, &loop, &QEventLoop::quit ); timer.start(10000); loop.exec(); if(timer.isActive()) { info.metadata = player.metaData(); info.metaDataIsValid = true; if(info.mediaFileType == video) { info.dt = info.metadata.value(QMediaMetaData::Date).toDateTime(); info.duration = info.metadata.stringValue(QMediaMetaData::Duration); info.videoFrameRate = info.metadata.stringValue(QMediaMetaData::VideoFrameRate); info.videoCodec = info.metadata.stringValue(QMediaMetaData::VideoCodec); info.audioCodec = info.metadata.stringValue(QMediaMetaData::AudioCodec); } info.resolution = info.metadata.value(QMediaMetaData::Resolution).toSize(); info.fileFormat = info.metadata.stringValue(QMediaMetaData::FileFormat); } else { //timer ran out but mediaStatusChanged was not emitted qDebug() << "meta data is not valid: "<<file; info.metaDataIsValid = false; } if(info.mediaFileType == image) { //QMediaMetaData does not contain any media file creation date info key QMediaMetaData::Date so I must use exifinfo to at least get this for jpg media files FileManipulator fm; easyexif::EXIFInfo exifinfo = fm.getExifInfo(file); QString s = QString(exifinfo.DateTimeOriginal.c_str()); if(s.length()==19) //YY:MM:DD HH:MM:SS { QStringList d = QString(exifinfo.DateTimeOriginal.c_str()).split(" "); QStringList date = d.at(0).split(":"); QStringList time = d.at(1).split(":"); QDate cdate(date.at(0).toInt(),date.at(1).toInt(), date.at(2).toInt()); info.dt.setDate(cdate); QTime ctime(time.at(0).toInt(), time.at(1).toInt(), time.at(2).toInt()); info.dt.setTime(ctime); } else { qDebug() << "no creation date exif info available for file: "<< file <<", must use QFileInfo::metadataChangeTime(), even though this often fails to provide creation date of the image"; info.dt = f.metadataChangeTime(); } } pbValue = pbValue + pbSteps; setProgressBarValue(pbValue); return info; }I am calling a member function of the class
MappedProcessor(which has severalQFutureWatchermembers, each one for one particular purpose, in this case the future watcher accumulatesQFuturewhich call the member functiongetMetaData)The function getMetaData unfortunatelly needs a QEventLoop due to the fact that
QMediaMetaDatarelies onQMediaPlayerand its signalQMediaPlayer::mediaStatusChangedto be sent until the meta data is available (I recycled this example https://stackoverflow.com/questions/69858215/read-qmediametadata-without-wait-after-set-the-media-source). So when callinggetMetaData(and I call it up to 50k+ times withQtConcurrent::mapped) , the event loop seems to be bound to main thread and the slot in connectionconnect(&player, &QMediaPlayer::mediaStatusChanged, &loop,&QEventLoop::quit);never gets called.I could implement a non-member function that does the same, tht should not be an issue but I dont know how i can make sure that an
QEventLoppin it gets associated with the thread that calls the function instead of the main thread.I filed a bug called [QMediaMetaData availability without having to wait for mediaStatusChanged() signal](link https://bugreports.qt.io/browse/QTBUG-120959) and I am looking for a workaround as not sure if this will ever be seen as bug or rather be seen as a low prio feature request.
Any ideas?
-
Hi, just guessing but have you tried to explicitly queue the connection, e.g.
... connect(&player, &QMediaPlayer::mediaStatusChanged, &loop,&QEventLoop::quit, Qt::QueuedConnection); ...@hskoglund
I just tried, it did not work. I understand the bug now (at least I believe I do):https://www.toptal.com/qt/qt-multithreading-c-plus-plus
- Tasks that don’t need the event loop. Specifically, the tasks that are not using signal/slot mechanism during the task execution.
Use: QtConcurrent and QThreadPool + QRunnable. - Tasks that use signal/slots and therefore need the event loop.
Use: Worker objects moved to + QThread.
I was hoping to avoid digging deeper into multithreading and instead using
QtConcurrent::mapped()but it seems I will have to dig deeper. - Tasks that don’t need the event loop. Specifically, the tasks that are not using signal/slot mechanism during the task execution.