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  &currentImageFullPath);
        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 &currentImageFullPath)
    {
        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);
        }
    }

  • Lifetime Qt Champion

    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 ?



  • @SGaist

    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.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.