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

0

Другие решения


По вопросам рекламы [email protected]