I'm having an issue using the QtConcurrent module for processing large images, specifically allowing the user to cancel processing an image once it has begun.
I've got two main classes involved in this process: - ProjectModel - a model class running in the UI thread that manages what images are part of the current project - ImageTileManager - a class that runs in a separate thread that handles requests from the project model to do the processing on the requested file, renders the result to a QImage, and then send the image back to the ProjectModel to display to the user. Everything works great when the user requests processing file(s) to image(s) and lets it run to completion. Where I'm having some trouble is when the user requests processing, but then changes their minds and cancels the job in progress. I'm hitting a lengthy delay when that occurs. Below is a sample run where I've enabled debug messages to track what's happening: 1. ProjectModel::slotImageRequested QTime("13:05:02.657") requesting "{82216618-dde4-41b1-bd6d-d991d9c6f4ca}" 2. ImageTileManager::queueJob QTime("13:05:02.658") queuing job "{82216618-dde4-41b1-bd6d-d991d9c6f4ca}" 3. ImageTileManager::processJob QTime("13:05:02.659") processing "{82216618-dde4-41b1-bd6d-d991d9c6f4ca}" 4. ProjectModel::slotCancelLoading QTime("13:05:04.792") canceling "{82216618-dde4-41b1-bd6d-d991d9c6f4ca}" 5. ImageTileManager::cancelJob QTime("13:05:11.534") canceling load on "{82216618-dde4-41b1-bd6d-d991d9c6f4ca}" 6. ProjectModel::slotImageRequested QTime("13:05:18.965") requesting "{449d7140-5bb2-4a6f-a3cf-98ba2529f237}" 7. ImageTileManager::cancelJob QTime("13:06:47.989") load canceled "{82216618-dde4-41b1-bd6d-d991d9c6f4ca}" 8. ImageTileManager::queueJob QTime("13:06:48.968") queuing job "{449d7140-5bb2-4a6f-a3cf-98ba2529f237}" 9. ImageTileManager::loadFinished QTime("13:06:48.969") load finished "LoadFutureWatcher {82216618-dde4-41b1-bd6d-d991d9c6f4ca}" 10. ImageTileManager::processJob QTime("13:06:48.969") processing "{449d7140-5bb2-4a6f-a3cf-98ba2529f237}" 11. ImageTileManager::loadFinished QTime("13:06:54.140") load finished "LoadFutureWatcher {449d7140-5bb2-4a6f-a3cf-98ba2529f237}" Debug lines explained: Lines 1 & 2: The user requests processing of a file in the UI (1), which is acknowledged by the processing class (2) Line 3: the processing class begins processing Lines 4 & 5: The user requests cancelation of the job (4), the processing class acknowledges and starts the cancellation process (5) Line 6: The user requests processing of a new job Line 7: the processing class has finally canceled the original job, roughly 1 minute, 36 seconds after the request to cancel Line 8: The processing class finally queues the request sent in (6) Line 9: The future watcher from the first job emits its finished() signal Lines 10 & 11: The processing class begins working on the second job (10) and is allowed to finish (11) The cancel() routine in the processing class looks like: void ImageTileManager::cancelJob(const QUuid &uuid) { QMutexLocker locker(&mJobQueueMutex); // check to see if it's our current job if(mCurrentJob.first == uuid) { if(mLoadWatcher->isRunning()) { mLoadWatcher->disconnect(); // disconnect signals from the QFutureWatcher qDebug() << __FUNCTION__ << QTime::currentTime() << "canceling load on" << uuid.toString(); // <- this produces message (5) above mLoadWatcher->cancel(); // tell QFutureWatcher to cancel the current run mLoadWatcher->waitForFinished(); // block until finished qDebug() << __FUNCTION__ << QTime::currentTime() << "load canceled" << uuid.toString(); // <- this produces message (6) above } } else { // delete queued job from the queue <snip> } } I've got two main questions: 1. What can I do to speed up the process of starting on the second job once the user requests canceling the first? I realize I'm using the blocking "waitForFinished()" call, but previous to this example, I wasn't blocking and instead reacting the the QFutureWatcher's finished() signal to indicate when the first job was finally canceled before starting the next job in the queue. Under that setup, it still takes roughly the same delay. The way I currently have my ImageTileManager designed, I only want it working on processing one image at a time, using QtConcurrent to throw all of the machine's cores at processing a single image as fast as possible, instead of trying to process multiple images simulataneously. I'm not sure if there's some sort of design change I can make where once a user elects to cancel a job, that job gets moved off into yet another thread (this time just for cleanup purposes) and the the ImageTileManager can launch the next QtConcurrent run on the next job in the queue, and then I just let the operating system handle the fight between the cleanup threads and the next's jobs processing threads? 2. Once I switched to using the blocking call, I added the "mLoadWatcher->disconnect()" call before I call cancel() on the QFutureWatcher, since I plan to block anyways and I no longer need to care about the finished() signal. However, if you note line 9 in the debug log, somehow there's still a signal/slot connection between the QFutureWatcher's finished() signal and my ImageTileManager::loadFinished() slot. This was unexpected, and I'm not sure how to prevent it? a. For what it's worth, I make the original connection like so: connect(mLoadWatcher, &QFutureWatcher<void>::finished, this, &ImageTileManager::loadFinished, static_cast<Qt::ConnectionType>(Qt::AutoConnection | Qt::UniqueConnection)); Thanks in advance, Sean This e-mail, including any attached files, may contain confidential information, privileged information and/or trade secrets for the sole use of the intended recipient. Any review, use, distribution, or disclosure by others is strictly prohibited. If you are not the intended recipient (or authorized to receive information for the intended recipient), please contact the sender by reply e-mail and delete all copies of this message. _______________________________________________ Interest mailing list Interest@qt-project.org https://lists.qt-project.org/listinfo/interest