Solved GUI not responsive while thread running
-
I am building an image viewer app. When a new folder is selected a separate thread is used to cache the images for fast previewing. Once the cache is filled previewing is very fast. However, while the cache is building on the worker thread the GUI response is slow, with up to a one second latency, which is about how long it can take to load a preview into the cache. I am running Qt 5.5.1 64bit MSVC. The program appears to work fine when compiled and run on OSX with little latency.
I would really appreciate it if someone could take a look at the code to see what I am doing wrong or could suggest a better method. I used the Qt mandelbrot example as a basis for the worker thread. How do I know a separate thread is actually running?
My MainWindow constructor includes:
imageCacheThread = new ImageCache(this, mdCache); connect(imageCacheThread, SIGNAL(showCacheStatus(const QImage)), this, SLOT(showCacheStatus(const QImage)));
MainWindow::newFolderSelected:
imageCacheThread->updateImageCache(thumbView-> thumbFileInfoList, imagePath);
imagecache.h
#ifndef IMAGECACHE_H #define IMAGECACHE_H #include "mdcache.h" #include <algorithm> // reqd to sort cache #include <QMutex> #include <QSize> #include <QThread> #include <QWaitCondition> class ImageCache : public QThread { Q_OBJECT public: ImageCache(QObject *parent, MetadataCache *mdCache); ~ImageCache(); void initImageCache(QFileInfoList &imageList); void updateImageCache(QFileInfoList &imageList, QString ¤tImageFullPath); QHash<QString, QImage> imCache; signals: void showCacheStatus(QImage imCacheStatus); private: QMutex mutex; QWaitCondition condition; bool restart; bool abort; MetadataCache *mdCache; // image cache struct CacheItem { int key; int origKey; QString fName; bool isCached; bool isTarget; int priority; float sizeMB; } cacheItem ; QList<CacheItem> cacheMgr; struct Cache { uint key; // current image uint prevKey; // used to establish directionof travel QString dir; // compare to input to see if different uint toCacheKey; // next file to cache uint toDecacheKey; // next file to remove fromc ache bool isForward; // direction of travel in folder float wtAhead; // ratio cache ahead vs behind int totFiles; // number of images available uint currMB; // the current MB consumed by the cache uint maxMB; // maximum MB available to cache uint folderMB; // MB required for all files in folder int targetFirst; // beginning of target range to cache int targetLast; // end of the target range to cache int pxTotWidth; // width in pixels of graphic in statusbar float pxUnitWidth; // width of one file on graphic in statusbar } cache; QList<uint>toCache; QList<uint>toDecache; bool loadImage(QString &imageFullPath, QImage &im); ulong getImCacheSize(); // add up total MB cached void setPriorities(int key); // based on proximity to current position and wtAhead void setTargetRange(); // define start and end key in the target range to cache bool nextToCache(); // find highest priority not cached bool nextToDecache(); // find lowest priority cached - return -1 if none cached static bool prioritySort(const CacheItem &p1, const CacheItem &p2); static bool keySort(const CacheItem &k1, const CacheItem &k2); void cacheStatus(); // update the cache status visual bar int pxMid(int key); // center current position on statusbar int pxStart(int key); // start current position on statusbar int pxEnd(int key); // end current position on statusbar void reportCacheManager(QString title); // for debugging }; #endif // IMAGECACHE_H
imagecache.cpp
#include "imagecache.h" ImageCache::ImageCache(QObject *parent, MetadataCache *mdCache) : QThread(parent) { this->mdCache = mdCache; restart = false; abort = false; } ImageCache::~ImageCache() { mutex.lock(); abort = true; condition.wakeOne(); mutex.unlock(); wait(); } // some support functions here for targeting, priorities, cache status update and cache initilization void ImageCache::updateImageCache(QFileInfoList &imageList, QString ¤tImageFullPath) { cache.key = imageList.indexOf(currentImageFullPath); cacheStatus(); if (cache.key == cache.prevKey && imageList.at(0).absolutePath() == cache.dir) return; cache.prevKey = cache.key; cache.isForward = (cache.key >= cache.prevKey); cache.currMB = getImCacheSize(); setPriorities(cache.key); setTargetRange(); // start or continue on the separate thread if (!isRunning()) { start(LowestPriority); } else { restart = true; condition.wakeOne(); } /* The target range is all the files that will fit into the available cache memory based on the cache priorities and direction of travel. We want to make sure the target range is cached, decaching anything outside the target range to make room as necessary. */ while (nextToCache()) { // is there room in cache uint room = cache.maxMB - cache.currMB; uint roomRqd = cacheMgr.at(cache.toCacheKey).sizeMB; while (room < roomRqd) { // make some room by removing lowest priority cached image if(nextToDecache()) { imCache.remove(cacheMgr[cache.toDecacheKey].fName); toDecache.removeFirst(); cacheMgr[cache.toDecacheKey].isCached = false; cache.currMB -= cacheMgr[cache.toDecacheKey].sizeMB; room = cache.maxMB - cache.currMB; } else break; } QImage *image = new QImage; QString imageFullPath = cacheMgr.at(cache.toCacheKey).fName; if (loadImage(imageFullPath, *image)) { // lock in case main thread uses image from cache mutex.lock(); imCache.insert(imageFullPath, *image); cacheMgr[cache.toCacheKey].isCached = true; mutex.unlock(); toCache.removeFirst(); cache.currMB = getImCacheSize(); cacheStatus(); } delete image; } cacheStatus(); } bool ImageCache::loadImage(QString &imageFullPath, QImage &image) { // get the embedded jpg in raw files, use image.load for cooked files QFileInfo fileInfo(imageFullPath); QString ext = fileInfo.completeSuffix().toLower(); if (ext == "cr2" || ext == "nef") { // raw files not handled by Qt QFile imFile(imageFullPath); imFile.open(QIODevice::ReadOnly); imFile.seek(mdCache->getOffsetFullJPG(imageFullPath)); QByteArray buf = imFile.read(mdCache->getLengthFullJPG(imageFullPath)); return image.loadFromData(buf, "JPEG"); } else { // cooked files return image.load(imageFullPath); } }
-
Hi,
From the looks of it, your code doesn't run in that other thread at all. You don't reimplement the run function (since you are basing your code on the Mandelbrot example)
Also, why allocate your QImage on the heap ?
By the way, why not use QPixmapCache ?
-
Thanks. I was a little confused about run so I took it out - LOL. I really appreciate the direction. It's working now!!
I'm allocating to the heap because a single QImage can exceed 200MB and I thought that might be too big to put on the stack.
I tried using QPixmap but the caching logic could not be customized. Can it be built in another thread? I've had to use QImage to avoid QPixmap, which I understand is not thread friendly.