Ошибка при вызове quit() для QCoreApplication в QThread

В попытке создать цикл событий Qt в отдельном потоке из библиотеки DLL, которая вызывается основным приложением, написанным на Java, я сделал следующее, основываясь на предложенном мною здесь предложении, которое работает довольно хорошо:

// 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, который автоматически создаст ToolThread с запущенным внутри него QCoreApplication:

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

Я предполагаю, что это потому, что после того, как я выполнил команду quit () для QCoreApplication, я должен немного подождать, пока он правильно закроет цикл обработки событий перед его удалением - как обычно бы вызывали quit (), а затем ждать () на обычном QThread перед его удалением. Тем не менее, QCoreApplication, похоже, не имеет эквивалента команды wait(), и я не могу реализовать QTimer для принудительной задержки, потому что она не будет работать, когда я закрою цикл обработки событий с помощью quit(). Поэтому я в недоумении, что делать. У меня есть предположение, что, поскольку QCoreApplication является QObject, я мог бы вызвать его метод deleteLater(), но не вижу, откуда мне его вызывать.

Есть ли эксперт, который понимает все тонкости QCoreApplication и QThread достаточно хорошо, чтобы предложить решение этой проблемы? Или есть принципиальный недостаток в том, как я это спроектировал?

1 ответ

Решение

Кажется, это сработало для меня...

Во-первых, я добавляю статическую функцию очистки в мое глобальное пространство имен:

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 я подключаю к нему сигнал aboutToQuit QCoreApplication:

    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 вызывает функцию очистки, пока его цикл обработки событий еще выполняется. Это планирует QCoreApplication для удаления самого себя, как только оно будет готово и готово, и в то же время сбрасывает глобальный указатель на NULL, чтобы остальная часть приложения знала, что новое QCoreApplication может быть создано при необходимости.

Я предполагаю, что это оставляет очень маленький риск того, что может возникнуть конфликт, если основное приложение немедленно создаст новое QCoreApplication и попытается запустить exec() на нем, пока старое QCoreApplication все еще находится в процессе очистки. Я не думаю, что это может произойти в контексте, где я его использую.

Другие вопросы по тегам