簡體   English   中英

在QThread內的QCoreApplication上調用quit()時出錯

[英]Error when calling quit() on a QCoreApplication within a QThread

為了從一個用Java編寫的主應用程序調用的DLL中的一個單獨線程中創建Qt事件循環,我根據在這里讀到的建議做了以下工作,該工作很好:

// Define a global namespace. We need to do this because the parameters 
// passed to QCoreApplication must have a lifetime exceeding that of the 
// QCoreApplication object
namespace ToolThreadGlobal
{
    static int argc = 1;
    static char * argv[] = { "MyVirtualMainApplication.exe", NULL };
    static QCoreApplication *coreApp = nullptr;
    static ToolThread *toolThread = nullptr;
};

//! The ToolThread class differs from a standard QThread only 
//! in its run() method
class ToolThread : public QThread
{
    //! Override QThread's run() method so that it calls 
    //! the QCoreApplication's exec() method rather than 
    //! the QThread's own
    void run() { ToolThreadGlobal::coreApp -> exec(); }
};

class ThreadStarter : public QObject
{
    Q_OBJECT

public:
    //! Constructor
    ThreadStarter()
    {
        // Set up the tool thread:
        if (!ToolThreadGlobal::toolThread)
        {
            ToolThreadGlobal::toolThread = new ToolThread();
            connect(ToolThreadGlobal::toolThread, &ToolThread::started,
                    this, &ThreadStarter::createApplication, Qt::DirectConnection);
                    // The thread's 'started' event is emitted after the thread
                    // is started but before its run() method is invoked. By 
                    // arranging for the createApplication subroutine to be 
                    // called before run(), we ensure that the required 
                    // QCoreApplication object is instantiated *within the 
                    // thread* before ToolThread's customised run() method 
                    // calls the application's exec() command.
            ToolThreadGlobal::toolThread->start();
        }
    }

    //! Destructor
    ~ThreadStarter()
    {
        // Ensure that the thread and the QCoreApplication are cleanly 
        // shut down:
        ToolThreadGlobal::coreApp -> quit();
        delete ToolThreadGlobal::coreApp;
        ToolThreadGlobal::coreApp = nullptr;
        delete ToolThreadGlobal::toolThread;
        ToolThreadGlobal::toolThread = nullptr;
    }

    //! Function to return a pointer to the actual tool thread:
    ToolThread* getThread() const { return ToolThreadGlobal::toolThread;  }

private:
    //! Create the QCoreApplication that will provide the tool 
    //! thread's event loop
    /*! This subroutine is intended to be called from the tool 
        thread itself as soon as the thread starts up.
    */
    void createApplication()
    {
        // Start the QCoreApplication event loop, so long as no 
        // other Qt event loop is already running
        if (QCoreApplication::instance() == NULL)
        {
            ToolThreadGlobal::coreApp = new QCoreApplication(ToolThreadGlobal::argc, 
                                                             ToolThreadGlobal::argv);
        }
    }
};

要使用此功能,從Java主應用程序線程調用的子例程只需創建一個ThreadStarter對象,該對象將自動創建一個在其中運行QCoreApplication的ToolThread:

itsThreadStarter = new ThreadStarter();
itsToolThread = itsThreadStarter -> getThread();

然后,我們可以以通常的方式實例化QObject類,將其移至線程並使用QMetaObject :: invokeMethod異步調用其方法:

itsWorker = new Worker();
itsWorker -> moveToThread(itsToolThread);

QMetaObject::invokeMethod(itsWorker, “doSomethingInteresting”);

完成后,我們只需刪除ThreadStarter對象,一切都將得到很好的清理。 除了討厭的消息說

WARNING: QApplication was not created in the main() thread

在啟動時,它似乎可以滿足我的所有要求。

除了……(這是我的問題了)。

有時-到目前為止我還沒有發現任何模式-在關機過程中出現錯誤。 通常發生在生產線上

        delete ToolThreadGlobal::coreApp;

但是有時候

    ToolThreadGlobal::coreApp -> exec();

(這當然是在線程的run()方法中執行的,直到ToolThreadGlobal :: coreApp-> quit();完全執行后才返回)。

錯誤消息通常是簡單的訪問沖突。 有時涉及更多:

ASSERT failure in QObjectPrivate::deleteChildren(): "isDeletingChildren already set, did this function recurse?", file ..\qtbase\src\corelib\kernel\qobject.cpp, line 1927

我認為這是因為,一旦我向QCoreApplication發出quit()命令,我應該等待一會兒,以便它在關閉事件循環之前將其正確關閉,就像通常調用quit()然后等待()在普通QThread上刪除。 但是,QCoreApplication似乎不具有wait()命令的等效功能,並且我無法實現QTimer來強制延遲,因為一旦用quit()關閉事件循環,它就將無法工作。 因此,我無所適從。 我有一個暗示,因為QCoreApplication是QObject,所以我可以調用它的deleteLater()方法,但是看不到應該從哪里調用它。

有沒有一位專家足夠了解QCoreApplication和QThread的來龍去脈,提出解決方案? 還是我設計此方法的根本缺陷?

這似乎對我有用...

首先,我向全局名稱空間添加了一個靜態的“清理”函數:

namespace ToolThreadGlobal
{
    static int argc = 1;
    static char * argv[] = { "MyVirtualMainApplication.exe", NULL };
    static QCoreApplication *coreApp = nullptr;
    static ToolThread *toolThread = nullptr;
    static void cleanup() { coreApp->deleteLater();  coreApp = nullptr; }
};

然后,從我的ThreadStarter :: createApplication插槽中,將QCoreApplication的aboutToQuit信號連接到它:

    void createApplication()
    {
        // Start the QCoreApplication event loop, so long as no other Qt event loop
        // is already running
        if (QCoreApplication::instance() == NULL)
        {
            ToolThreadGlobal::coreApp = new QCoreApplication(ToolThreadGlobal::argc, 
                                                             ToolThreadGlobal::argv);
            connect(ToolThreadGlobal::coreApp, &QCoreApplication::aboutToQuit,
                    ToolThreadGlobal::cleanup);
        }
    }

然后將“ ThreadStarter”析構函數減少到僅五行(包括添加對QThread :: quit()和QThread :: wait()的調用,這應該是第一次出現):

    ~ThreadStarter()
    {
        // Ensure that the thread and the QCoreApplication are cleanly shut down:
        ToolThreadGlobal::coreApp -> quit();
        ToolThreadGlobal::toolThread -> quit();
        ToolThreadGlobal::toolThread -> wait();
        delete ToolThreadGlobal::toolThread;
        ToolThreadGlobal::toolThread = nullptr;
    }

當ThreadStarter析構函數調用QCoreApplication :: quit()時,QCoreApplication會在事件循環仍在運行時調用cleanup函數。 這樣可以安排QCoreApplication在狀態良好並准備就緒后刪除自身,同時將全局指針重置為NULL,以便應用程序的其余部分知道可以在需要時實例化新的QCoreApplication。

我想如果主應用程序立即創建一個新的QCoreApplication並嘗試在舊的QCoreApplication仍在清理自身的過程中嘗試在其上運行exec(),那么這可能會產生沖突。 我認為在我使用它的情況下不太可能發生這種情況。

暫無
暫無

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

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