[Interest] Issue with QFutureWatcher cancel() delay
Sean Murphy
Sean.M.Murphy at us.kbr.com
Fri Sep 23 20:01:38 CEST 2022
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.
More information about the Interest
mailing list