Home > Enterprise >  How to stop long operation on QThread?
How to stop long operation on QThread?

Time:05-21

I've made some research but I couldn't find the answer to why my solution is not working. But to the point.

I've got QWidget as a separate dialog in my app. I'm using this QWidget class to gather paths to files and zip them into one file with ZipLib. To give users some feedback about the progress of the zipping I've added QProgressBar which is updated during zipping. But I've found out that in some cases when files are too big, zipping files are taking very long and makes my app not respond during this time. So my idea was to move the long operation of zipping to another thread using QThread and everything is working fine, zipping is working, and the progress bar is updated BUT there is a problem when I want to cancel the zipping operation. With my current approach action with zipping just don't listen to any of my requests to interrupt the thread and the thread is doing zipping even after I've closed my dialog. I'll show you my approach maybe there is something very trivial but I just can't make to cancel this zipping operation.

tl;dr version: I can't interrupt QThread with requestInterruption() or any other way.

This is my WorkerThread class:

class WorkerThread : public QThread
{
Q_OBJECT

public:
    void setup(std::string zip, std::vector<std::string> file)
    {
        mZip = zip;
        mFiles = file;
        mActionStopped = false;
    }

    void run() override {
        size_t progressValue = (100 / mFiles.size());

        try
        {
            for (const auto& file : mFiles)
            {
                if (not isInterruptionRequested())
                {
                    Q_EMIT updateTheProgress(progressValue);
                    ZipFile::AddFile(mZip, file);
                }
                else
                {
                    return;
                }
            }
        }
        catch(const std::exception& e)
        {
            Q_EMIT resultReady(false);
        }

        if (not mActionStopped)
        {
            Q_EMIT updateTheProgress(100 - actualProgress); // To set 100%
            Q_EMIT resultReady(true);
        }
    }

    std::string mZip;
    std::vector<std::string> mFiles;
    size_t actualProgress = 0;
    bool mActionStopped;

Q_SIGNALS:
    void resultReady(bool dupa);
    void updateProgress(int value);

public Q_SLOTS:
    void updateTheProgress(int value)
    {
        actualProgress  = value;
        Q_EMIT updateProgress(actualProgress);
    }

    void stopWork()
    {
        mActionStopped = true;
    }
};

And in my QWidget class I've got something like this:

workerThread = new WorkerThread();
connect(workerThread, &WorkerThread::resultReady, this, &ZipProjectDialog::zipDone);
connect(workerThread, SIGNAL(updateProgress(int)), progressBar, SLOT(setValue(int)));

connect(btnBox, &QDialogButtonBox::rejected, workerThread, &WorkerThread::requestInterruption);
connect(btnBox, &QDialogButtonBox::rejected, workerThread, &WorkerThread::stopWork);

workerThread->setup(zipFilename, filePathList);
workerThread->start();
connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);

I've followed QThread documentation with this but requestInterruption() is still not working.

If someone has any idea how to resolve this I'll appreciate it.

Thanks!

CodePudding user response:

You cannot interrupt the execution of

ZipFile::AddFile(mZip, file);

itself. It is a single call to a function, and no one checks isInterruptionRequested() inside that function.

A simplified example

#include <QCoreApplication>
#include <QDebug>
#include <QThread>
#include <QTimer>

class MyThread : public QThread
{
public:
    void run() override
    {
        qDebug() << "starting";
        for (int i = 0; i < 10;   i) {
            qDebug() << "loop" << i;
            if (!isInterruptionRequested())
                sleep(5);
            else
                break;
        }
        qDebug() << "finished";
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    MyThread thread;
    QTimer::singleShot(0, &thread, [&thread] {
        thread.start();
        QTimer::singleShot(7000 /*7 seconds*/, &thread, [&thread] {
            qDebug() << "interrupting";
            thread.requestInterruption();
        });
    });

    return a.exec();
}

prints

starting
loop 0
loop 1
interrupting
loop 2
finished

as expected. And as expected there are a few seconds delay between "interrupting" and "loop 2", and no delay between "loop 2" and "finished".

If you want to interrupt your task more granular, then you have to find a more granular API than ZipFile::AddFile, so you can check isInterruptionRequested() regularly even while a single file is archived, or use an external process that can be killed instead.

  • Related