简体   繁体   English

在QThread内的QCoreApplication上调用quit()时出错

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

In an effort to create a Qt event loop in a separate thread, from within a DLL which is called by a main application written in Java, I have done the following, based on a suggestion I read here , which works rather well: 为了从一个用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);
        }
    }
};

To use this, a subroutine called from the main Java applications' thread just needs to create a ThreadStarter object which will automatically create a ToolThread with a QCoreApplication running inside it: 要使用此功能,从Java主应用程序线程调用的子例程只需创建一个ThreadStarter对象,该对象将自动创建一个在其中运行QCoreApplication的ToolThread:

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

We can then instantiate a QObject class in the usual way, move it to the thread and call its methods asynchronously using QMetaObject::invokeMethod: 然后,我们可以以通常的方式实例化QObject类,将其移至线程并使用QMetaObject :: invokeMethod异步调用其方法:

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

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

When we're done, we just delete the ThreadStarter object and everything is cleaned up nicely. 完成后,我们只需删除ThreadStarter对象,一切都将得到很好的清理。 Apart from the annoying message saying 除了讨厌的消息说

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

on startup, it seems to meet all my requirements. 在启动时,它似乎可以满足我的所有要求。

Except… (and here, at last, is my question). 除了……(这是我的问题了)。

Occasionally - and without any pattern that I've been able to discern so far - I get an error during the shutdown process. 有时-到目前为止我还没有发现任何模式-在关机过程中出现错误。 Usually it occurs at the line 通常发生在生产线上

        delete ToolThreadGlobal::coreApp;

but sometimes at the line 但是有时候

    ToolThreadGlobal::coreApp -> exec();

(which of course is executed in the thread's run() method and doesn't return until after ToolThreadGlobal::coreApp -> quit(); has been fully executed). (这当然是在线程的run()方法中执行的,直到ToolThreadGlobal :: coreApp-> quit();完全执行后才返回)。

Often the error message is a simple access violation; 错误消息通常是简单的访问冲突。 sometimes it's a rather more involved: 有时涉及更多:

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

I assume it's because, once I issue the quit() command to the QCoreApplication, I should be waiting for a little while for it to close down the event loop properly before deleting it - just as one would usually call quit() and then wait() on an ordinary QThread before deleting it. 我认为这是因为,一旦我向QCoreApplication发出quit()命令,我应该等待一会儿,以便它在关闭事件循环之前将其正确关闭,就像通常调用quit()然后等待()在普通QThread上删除。 However, QCoreApplication doesn't seem to have the equivalent of a wait() command, and I can't implement a QTimer to force a delay because it wouldn't work once I've closed down the event loop with quit(). 但是,QCoreApplication似乎不具有wait()命令的等效功能,并且我无法实现QTimer来强制延迟,因为一旦用quit()关闭事件循环,它就将无法工作。 I'm therefore at a loss what to do. 因此,我无所适从。 I have an inkling that, as QCoreApplication is a QObject, I could call its deleteLater() method but I can't see where I should call it from. 我有一个暗示,因为QCoreApplication是QObject,所以我可以调用它的deleteLater()方法,但是看不到应该从哪里调用它。

Is there an expert out there who understands the ins and outs of QCoreApplication and QThread well enough to suggest a solution to this? 有没有一位专家足够了解QCoreApplication和QThread的来龙去脉,提出解决方案? Or is there a fundamental flaw in the way that I have designed this? 还是我设计此方法的根本缺陷?

This seems to have worked for me... 这似乎对我有用...

Firstly, I add a static 'cleanup' function to my global namespace: 首先,我向全局名称空间添加了一个静态的“清理”函数:

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; }
};

Then, from my ThreadStarter::createApplication slot I connect the QCoreApplication's aboutToQuit signal to it: 然后,从我的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);
        }
    }

Then the 'ThreadStarter' destructor is reduced to just five lines (including the addition of calls to QThread::quit() and QThread::wait() which should have been there the first time around): 然后将“ 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;
    }

When the ThreadStarter destructor calls QCoreApplication::quit(), the QCoreApplication calls the cleanup function while its event loop is still running. 当ThreadStarter析构函数调用QCoreApplication :: quit()时,QCoreApplication会在事件循环仍在运行时调用cleanup函数。 This schedules the QCoreApplication to delete itself once it is good and ready, and in the meantime resets the global pointer to NULL so that the rest of the application knows that a new QCoreApplication can be instantiated when needed. 这样可以安排QCoreApplication在状态良好并准备就绪后删除自身,同时将全局指针重置为NULL,以便应用程序的其余部分知道可以在需要时实例化新的QCoreApplication。

I guess this leaves a very small risk that there could be a conflict if the main application immediately creates a new QCoreApplication and tries to run exec() on it while the old QCoreApplication is still in the process of cleaning itself up. 我想如果主应用程序立即创建一个新的QCoreApplication并尝试在旧的QCoreApplication仍在清理自身的过程中尝试在其上运行exec(),那么这可能会产生冲突。 I don't think that is likely to happen in the context where I'm using it. 我认为在我使用它的情况下不太可能发生这种情况。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM