簡體   English   中英

Qt 線程執行在 exit() quit() 或 terminate() 被調用后不會停止

[英]Qt thread execution does not stop after exit() quit() or terminate() was called

首先,我已經閱讀了QThread並使用了QEventLoop,但我並不完全確定我的實現是正確的。

TL;DR 請參閱下面的問題詳細信息

最有用的信息來源是Qt WikiKDAB Qthread演示(對 w/ & w/o 事件循環很有用)、與此問題相關的 SO 帖子herehere

我的場景是:

我有一個可能運行很長時間的函數,其中包含多個 I/O 磁盤調用。 因此我需要一個線程來不阻塞 UI。 為此,我自己實現了一個線程。

TL;DR QThreads

我的理解是QThread是一個單獨的事件循環對象,需要自定義run()實現或將對象移動到新創建的線程對象,其中移動的對象存在(和運行)。 我所描述的是帶事件循環實現的。

問題

我可能會遺漏一些東西,因為我的實現,我上面描述的,不能正常工作。 我怎么知道這一點,以及上面的 Qt Docs 和 SO 帖子提到QThread::quit()QThread::exit()依賴於QEventLoop ,並且如果QThread::exec()沒有運行(通過調用QThread::run()通過QThread::start() ),那么quit()exit()函數將永遠不會運行,這是我的問題

我的實現哲學類似於 Java 的 Thread & Lambda 語法,例如

new Thread(() -> { // some code to run in a different thread}).start();

我使用了以下實現

可以使用 lambda 的各種線程對象容器

QWorkerThread: public QObject

    // This is the thread that runs object below
----QWaitThread : public QThread

    // This is the object which lives inside the above thread
----ThreadWorker : public QObject, public QInterruptable

簡單的示例用法是(在QWorkerThread內完成線程和子對象清理):

QWorkerThread *workerThread = new QWorkerThread;
workerThread->setRunnable([](){
    // insert CPU intensive or long running task here
});
workerThread->start();

問題詳細信息/示例

// somewhere in main UI thread
workerThread->stop(); // or workerThread->kill() 

它調用QThread::quit()QThread::quit() ,然后QThread::terminate()后跟QThread::wait()將不會終止線程。 lambda 中定義的長時間運行的進程(在setRunnable() )將一直運行直到它完成。

我知道這篇文章比傳統文章要長,但我希望每個人都能全面了解我想要實現的目標,因為我不確定我的問題究竟出在哪里。

任何幫助將不勝感激!


代碼實現

我將發布所有代碼以獲得完整的實現思路,以防我錯過一些重要的事情。

QWaitThread.h是一個QThread的實現

#ifndef QWAITTHREAD_H
#define QWAITTHREAD_H

#include <QObject>
#include <QThread>
#include <QWaitCondition>
#include <QMutex>

class QWaitThread : public QThread
{
    Q_OBJECT
public:
    explicit QWaitThread(QObject *parent = nullptr);
    ~QWaitThread();
    virtual void pause();
    virtual void resume();    

signals:
    void paused();
    void resumed();

public slots:
    void pausing();
    void resuming();

private:
    QWaitCondition *waitCondition;
    QMutex mutex;
};

#endif // QWAITTHREAD_H

QWaitThread.cpp

#include "qwaitthread.h"

QWaitThread::QWaitThread(QObject *parent) : QThread(parent)
{
    waitCondition = new QWaitCondition;
}

QWaitThread::~QWaitThread()
{
    if(waitCondition != nullptr) {
        delete waitCondition;
    }
}

void QWaitThread::pause()
{
    emit paused();
    waitCondition->wait(&mutex);
}

void QWaitThread::resume()
{
    waitCondition->wakeAll();
    emit resumed();
}

void QWaitThread::pausing()
{
    pause();
}

void QWaitThread::resuming()
{
    resume();
}

QInterruptable.h接口定義了一些預期的功能

#ifndef QINTERRUPTABLE_H
#define QINTERRUPTABLE_H

class QInterruptable {
public:
    virtual void pause() = 0;
    virtual void resume() = 0;
    virtual void interrupt() = 0;
    virtual ~QInterruptable() = default;
};

#endif // QINTERRUPTABLE_H

ThreadWorker.h是在QWaitThread中生存(和運行)的QWaitThread

#ifndef THREADWORKER_H
#define THREADWORKER_H

#include <QObject>
#include <functional>
#include <QWaitCondition>
#include <QMutex>

#include "QInterruptable.h"

class ThreadWorker : public QObject, public QInterruptable
{
    Q_OBJECT

private:
    QMutex mutex;
    QWaitCondition *waitCondition;
    std::function<void ()> runnable;
    bool shouldPause = false;

public:
    explicit ThreadWorker(QObject *parent = nullptr);
    ThreadWorker(std::function<void ()> func);
    ~ThreadWorker();

    void setRunnable(const std::function<void ()> &value);

signals:
    /**
     * Emitted when the QWorkerThread object has started work
     * @brief started
     */
    void started();

    /**
     * @brief progress reports on progress in method, user defined.
     * @param value reported using int
     */
    void progress(int value);

    /**
     * Emitted when the QWorkerThread object has finished its work, same signal is used from &QThread::finished
     * @brief started
     */
    void finished();

    /**
     * Emitted when the QWorkerThread has encountered an error, user defined.
     * @brief started
     */
    void error();

public slots:
    virtual void run();
    virtual void cleanup();


    // QInterruptable interface
public:
    void pause()
    {
        shouldPause = true;
    }
    void resume()
    {
        shouldPause = false;
    }
    QMutex& getMutex();
    QWaitCondition *getWaitCondition() const;
    void setWaitCondition(QWaitCondition *value);
    bool getShouldPause() const;

    // QInterruptable interface
public:
    void interrupt()
    {

    }
};

#endif // THREADWORKER_H

線程工作者.cpp

#include "threadworker.h"

void ThreadWorker::setRunnable(const std::function<void ()> &value)
{
    runnable = value;
}

QMutex& ThreadWorker::getMutex()
{
    return mutex;
}

QWaitCondition *ThreadWorker::getWaitCondition() const
{
    return waitCondition;
}

void ThreadWorker::setWaitCondition(QWaitCondition *value)
{
    waitCondition = value;
}

bool ThreadWorker::getShouldPause() const
{
    return shouldPause;
}

ThreadWorker::ThreadWorker(QObject *parent) : QObject(parent)
{
    waitCondition = new QWaitCondition;
}

ThreadWorker::ThreadWorker(std::function<void ()> func): runnable(func) {
    waitCondition = new QWaitCondition;
}


ThreadWorker::~ThreadWorker()
{    
    if(waitCondition != nullptr){
        delete waitCondition;
    }
}

void ThreadWorker::run()
{
    emit started();
    runnable();
    emit finished();
}

void ThreadWorker::cleanup()
{

}

QWorkerThread.h 感興趣的主要類,其中接受可運行的lambda 以及主“線程”處理發生的地方,移動到線程,啟動線程,處理事件等

#ifndef QWORKERTHREAD_H
#define QWORKERTHREAD_H

#include <QObject>
#include <functional>
#include <QThread>
#include <QEventLoop>

#include "qwaitthread.h"
#include "threadworker.h"

class QWorkerThread: public QObject
{
    Q_OBJECT

public:

    enum State {
        Running,
        Paused,
        NotRunning,
        Finished,
        Waiting,
        Exiting
    };

    QWorkerThread();
    explicit QWorkerThread(std::function<void ()> func);
    ~QWorkerThread();
    static QString parseState(QWorkerThread::State state);
    virtual void setRunnable(std::function <void()> runnable);
    virtual void start(QThread::Priority priority = QThread::Priority::InheritPriority);
    virtual void stop();
    virtual void wait(unsigned long time = ULONG_MAX);
    virtual void kill();
    virtual void setWorkerObject(ThreadWorker *value);

    virtual void pause();
    virtual void resume();
    virtual QWaitThread *getWorkerThread() const;

    State getState() const;


signals:
    /**
     * Emitted when the QWorkerThread object has started work
     * @brief started
     */
    void started();

    /**
     * @brief progress reports on progress in method, user defined.
     * @param value reported using int
     */
    void progress(int value);

    /**
     * Emitted when the QWorkerThread object has finished its work, same signal is used from &QThread::finished
     * @brief started
     */
    void finished();

    /**
     * Emitted when the QWorkerThread has encountered an error, user defined.
     * @brief started
     */
    void error();

private:
    /**
     * @brief workerObject - Contains the object and 'method' that will be moved to `workerThread`
     */
    ThreadWorker *workerObject = nullptr;

    /**
     * @brief workerThread - Worker Thread is seperate thread that runs the method
     */
    QWaitThread *workerThread = nullptr;

    State state = State::NotRunning;

};

#endif // QWORKERTHREAD_H

QWorkerThread.cpp實現

#include "qworkerthread.h"

QWorkerThread::QWorkerThread()
{
    state = State::NotRunning;
    workerThread = new QWaitThread;
    workerObject = new ThreadWorker;
    workerThread->setObjectName("WorkerThread");
}

QWorkerThread::QWorkerThread(std::function<void ()> func)
{
    state = State::NotRunning;
    workerThread = new QWaitThread;
    workerObject = new ThreadWorker(func);
    workerThread->setObjectName("WorkerThread");
}

QWorkerThread::~QWorkerThread()
{
    //  Check if worker thread is running
    if(workerThread->isRunning()) {

        // Exit thread with -1
        workerThread->exit(-1);
    }

    if(!workerThread->isFinished()) {
        workerThread->wait(500);

        if(workerThread->isRunning()) {
            workerThread->terminate();
        }
    }

    // cleanup
    delete workerObject;
    delete workerThread;
}

void QWorkerThread::setRunnable(std::function<void ()> runnable)
{
    workerObject->setRunnable(runnable);
}

void QWorkerThread::start(QThread::Priority priority)
{

    state = State::Running;
    // Connect workerThread start signal to ThreadWorker object's run slot
    connect(workerThread, &QThread::started, workerObject, &ThreadWorker::started);
    connect(workerThread, &QThread::started, workerObject, &ThreadWorker::run);

    // Connect threadWorker progress report to this progress report
    connect(workerObject, &ThreadWorker::progress, this, &QWorkerThread::progress);

    // Cleanup
    connect(workerObject, &ThreadWorker::finished, this, [this](){
        state = State::Finished;
        emit finished();
    });
    connect(workerThread, &QWaitThread::finished, this, [this] {
        workerObject->deleteLater();
    });

    // move workerObject to thread
    workerObject->moveToThread(workerThread);

    // emit signal that we are starting
    emit started();

    // Start WorkerThread which invokes object to start process method
    workerThread->start(priority);
}

void QWorkerThread::stop()
{
    state = State::Exiting;
    // Exit thread safely with success
    workerThread->quit();

    emit finished();
}

void QWorkerThread::wait(unsigned long time)
{
    state = State::Waiting;
    workerThread->wait(time);
}

void QWorkerThread::kill()
{
    // try stopping
    stop();

    // check if still running
    if(workerThread->isRunning()){
        // forcefully kill
        workerThread->terminate();
        workerThread->wait();
    }

    emit finished();
}

void QWorkerThread::setWorkerObject(ThreadWorker *value)
{
    workerObject = value;
}

QWaitThread *QWorkerThread::getWorkerThread() const
{
    return workerThread;
}

QWorkerThread::State QWorkerThread::getState() const
{
    return state;
}

QString QWorkerThread::parseState(QWorkerThread::State state) {
    switch (state) {
        case Running:
            return "Running";
        case Paused:
            return "Paused";
        case NotRunning:
            return "NotRunning";
        case Finished:
            return "Finished";
        case Waiting:
            return "Waiting";
        case Exiting:
            return "Exiting";
    }

    return QString("Unknown State [%1]").arg(QString::number(state)) ;
}

void QWorkerThread::pause()
{
    workerObject->pause();
    state = State::Paused;
}

void QWorkerThread::resume()
{
    workerObject->resume();
    state = State::Running;
}

更新一些額外信息

關於~QWorkerThread() ,我注意到當調用delete QThreadQThread::deleteLater()QWaitThread() (或QThread )將拋出一個致命錯誤:線程在仍在運行時被破壞。 這是在調用quit() / terminate()之后。

來自QThread.cpp的以下行

if (d->running && !d->finished && !d->data->isAdopted)
    qFatal("QThread: Destroyed while thread is still running");

在哪里

d->running == true
d->finished == false
d->data->isAdopted ?

我已經測試了你的代碼,這是我意識到的。
正如您所提到的, terminate()不會完全停止線程。 Qt 文檔說:

終止線程的執行。 線程可能會也可能不會立即終止,這取決於操作系統的調度策略。 可以肯定的是,在terminate() QThread::wait()之后使用QThread::wait()

不幸的是,即使在terminate()之后, wait()也會凍結。 您的代碼可能有問題,但我創建了一個最大程度簡化的示例來驗證這一點,它仍然存在相同的問題。

首先,這是我建議更改的代碼部分:

QWorkerThread::~QWorkerThread()
{
    ...
    // cleanup
    delete workerObject; // Unsafe, but the only way to call the destructor, if necessary
    delete workerThread; // qFatal
}

以下是 Qt 文檔關於析構函數不安全性的說明:

從擁有對象的線程以外的線程(或以其他方式訪問對象)調用QObject上的 delete 是不安全的,除非您保證該對象在那一刻不處理事件。 使用QObject::deleteLater()代替,將發布一個DeferredDelete事件,該事件最終將被對象線程的事件循環接收。 默認情況下,擁有該線程QObject是創建線程QObject ,但並非后QObject::moveToThread()被調用。

筆記。 在沒有qFatal情況下,將delete workerThread更改為workerThread->deleteLater()對我qFatal


好的,我們實際上有什么問題:

  1. 由於qFatal,在terminate()之后不能直接調用QThread子類的qFatal
  2. 盡管有文檔, wait()凍結並且在terminate()之后無法使用

(似乎只有在將無限操作移入事件循環時才實際出現問題)

確保問題不在代碼的另一個位置 最小可重現示例

工人.h

#pragma once

#include <QObject>

class Worker : public QObject
{
    Q_OBJECT

public:
    ~Worker();

public slots:
    void process();
};

工人.cpp

#include "Worker.h"
#include <QThread>
#include <QDebug>
#include <QDateTime>

Worker::~Worker()
{
    qDebug() << "~Worker()";
}

void Worker::process()
{
    qDebug("Hello World!");

    while(true)
    {
        qDebug() << QDateTime::currentDateTime();
        QThread::msleep(100);
    }
}

主Win.h

#pragma once

#include <QtWidgets/QMainWindow>

class QThread;
class Worker;

class MainWin : public QMainWindow
{
    Q_OBJECT

public:
    MainWin(QWidget *parent = nullptr);
    ~MainWin();

private:
    QThread*    thread = nullptr;
    Worker*     worker = nullptr;
};

主Win.cpp

#include "MainWin.h"
#include "Worker.h"
#include <QThread>
#include <QDebug>
#include <QDateTime>

MainWin::MainWin(QWidget *parent)
  : QMainWindow(parent)
{
    thread = new QThread;
    worker = new Worker;

    worker->moveToThread(thread);

    // Start only one infinite operation
    connect(thread, &QThread::started, worker, &Worker::process);

    thread->start();
}

MainWin::~MainWin()
{
    if (thread->isRunning())
    {
        thread->exit(-1);
        thread->wait(500);
    }

    if (thread->isRunning())
    {
        thread->terminate();
    }

    //cleanup
    delete worker;
    delete thread; // qFatal("QThread: Destroyed while thread is still running")
}

我發現的唯一工作代碼

MainWin::~MainWin()
{
    ...
    //cleanup
    delete worker; // Worker destructor will be called, but be note this is unsafe
    thread->deleteLater(); // Allows to avoid qFatal but make thread terminated
}

結論和建議

除了完全避免terminate()之外,我所能提供的一切就是使用terminate()而不使用wait()然后workerThread->deleteLater()

如果您嘗試終止的耗時操作是您自己的代碼,請考慮在代碼中嵌入一些終止標志。

在可能的情況下,最好避免使用原始指針並用智能指針替換它們。


我還能提供什么作為在線程中運行 lambda 的通用方法

簡化示例如何使用 lamdas、信號槽、線程、啟動完成信號、 QtConcurrent::run()QFuture<> 通過這種方式,您既可以在一個持久性附加線程中運行代碼,也可以在自動線程池中運行代碼。 但不支持終止。

LambdaThread.h

#pragma once

#include <QObject>
#include <functional>
#include <QFuture>

class QThreadPool;

class LambdaThread : public QObject
{
    Q_OBJECT

public:
    // maxThreadCount = -1 to use idealThreadCount by default
    LambdaThread(QObject *parent, int maxThreadCount = -1);

signals:
    void started();
    void finished();

public slots:
    // Invoke this directly or by a signal
    QFuture<void> setRunnable(std::function<void()> func);

private:
    /*
    For the case you need persistent thread sometimes.
    In the case you never need persistent thread,
    just remove m_threadPool from this class at all
    */
    QThreadPool* m_threadPool = nullptr;
};

LambdaThread.cpp

#include "LambdaThread.h"
#include <QtConcurrent/QtConcurrent>
#include <QThreadPool>

LambdaThread::LambdaThread(QObject *parent, int maxThreadCount /*= -1*/)
    : QObject(parent)
{
    m_threadPool = new QThreadPool(this);

    if(maxThreadCount > 0)
    {
        m_threadPool->setMaxThreadCount(maxThreadCount);

        if (maxThreadCount == 1)
        {
            // Avoid thread affinity changing
            m_threadPool->setExpiryTimeout(-1);
        }
    }
}

QFuture<void> LambdaThread::setRunnable(std::function<void()> func)
{
    return QtConcurrent::run(m_threadPool,
        [this, func]()
    {
        // Be note that you actually need event loop in a receiver thread only
        emit started();

        func();

        emit finished();
    });
}

只是 GUI 類示例,您可以在其中啟動可運行程序並接收信號。

主Win.h

#pragma once

#include <QtWidgets/QMainWindow>
#include <functional>

class LambdaThread;

class MainWin : public QMainWindow
{
    Q_OBJECT

public:
    MainWin(QWidget *parent = nullptr);

signals:
    // For the case you want to use signals
    void newRunnable(std::function<void()> func);

private:
    LambdaThread* m_lambdaThread = nullptr;
};

主Win.cpp

#include "MainWin.h"
#include "LambdaThread.h"
#include <QFuture>
#include <QDebug>

MainWin::MainWin(QWidget *parent)
    : QMainWindow(parent)
{
    m_lambdaThread = new LambdaThread(this);

    connect(this, &MainWin::newRunnable,
        m_lambdaThread, &LambdaThread::setRunnable);

    /*
    Do not forget the third (`this`) context variable
    while using modern signal-slot connection syntax with lambdas
    */
    connect(m_lambdaThread, &LambdaThread::started,
        this, []()
    {
        qDebug() << "Runnable stated";
    });

    connect(m_lambdaThread, &LambdaThread::finished,
        this, []()
    {
        qDebug() << "Runnable finished";
    });

    // Set your lambda directly
    QFuture<void> future = m_lambdaThread->setRunnable([]()
    {
        qDebug() << "hello from threaded runnable";
    });

    // You can also use future (not necessary of course)
    //future.waitForFinished();

    // Or you can emit your lambda via the signal:
    emit newRunnable([]()
    {
        qDebug() << "hello from threaded runnable which comes from signal";
    });
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM