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