Reputation: 21
I'm currently updating an existing codebase designed to be used with a GTK GUI to QT, so that it can implement multi threading, as the functions take hours to complete.
This codebase makes frequent calls to a function display(std::string)
, for the purpose of updating a text display widget. I redefined this function for the new QT version:
In Display.cpp:
void display(std::string output)
{
//
MainWindow * gui = MainWindow::getMainWinPtr(); //Gets instance of GUI
gui->DisplayInGUI(output); //Sends string to new QT display function
}
In MainWindow.cpp:
void MainWindow::DisplayInGUI(std::string output)
{
//converts output to qstring and displays in text edit widget
}
void MainWindow::mainFunction(){
//calls function in existing codebase, which itself is frequently calling display()
}
void MainWindow::on_mainFunctionButton_released()
{
QFuture<void> future = QtConcurrent::run(this,&MainWindow::mainFunction);
}
If I run the main function in a new thread, display(std::string)
won't update the GUI until the thread completes. I understand why; the GUI can only be updated in the main thread. Everything else functions as intended.
What I want to implement, but I'm not sure how, is having display(std:string)
send a signal back to the main thread to call MainWindow::DisplayInGUI(output_text)
with the string that was passed to the display() function. I believe this is the correct way to do it, but correct me if I'm wrong. I want to avoid changing the existing codebase at all costs.
EDIT: I should add that for some dumb reasons entirely out of my control, I am forced to use C++98 (yeah, I know)
Upvotes: 1
Views: 2201
Reputation: 98505
First of all: there's no way to make the getMainWinPtr
method thread-safe, so this pseudo-singleton hack should probably go away. You can pass around some application-global context to all the objects that do application-global things like provide user feedback. Say, have a MyApplication : QObject
(don't derive from QApplication
, it's unnecessary). This can be passed around when new objects are created, and you'd then control the relative lifetime of the involved objects directly in the main()
function:
void main(int argc, char **argv) {
QApplication app(argc, argv);
MainWindow win;
MyApplication foo;
win.setApplication(&foo);
// it is now guaranteed by the semantics of the language that
// the main window outlives `MyApplication`, and thus `MyApplication` is free to assume
// that the window exists and it's OK to call its methods
...
return app.exec();
}
Of course MyApplication
must take care that the worker threads are stopped before its destructor returns.
To communicate asynchronous changes to QObject
living in (non-overloaded) QThread
s (including the main thread), leverage the built-in inter-thread communication inherent in Qt's design: the events, and the slot calls that traverse thread boundaries.
So, given the DisplayInGUI
method, you need a thread-safe way of invoking it:
std::string newOutput = ...;
QMetaObject::invokeMethod(mainWindow, [mainWindow, newOutput]{
mainWindow->displayInGUI(newOutput);
});
This takes care of the thread-safety aspect. Now we have another problem: the main window can get hammered with those updates much faster than the screen refresh rate, so there's no point in the thread notifying the main window more often than some reasonable rate, it'll just waste resources.
This is best handled by making the DisplayInGUI
method thread-safe, and leveraging the timing APIs in Qt:
class MainWindow : public QWidget {
Q_OBJECT
...
static constexpr m_updatePeriod = 1000/25; // in ms
QMutex m_displayMutex;
QBasicTimer m_displayRefreshTimer;
std::string m_newDisplayText;
bool m_pendingRefresh;
...
void timerEvent(QTimerEvent *event) override {
if (event->timerId() == m_displayRefreshTimer.timerId()) {
QMutexLocker lock(&m_displayMutex);
std::string text = std::move(m_newDisplayText);
m_pendingRefresh = false;
lock.release();
widget->setText(QString::fromStdString(text));
}
QWidget::timerEvent(event);
}
void DisplayInGUI(const std::string &str) {
// Note pass-by-reference, not pass-by-value. Pass by value gives us no benefit here.
QMutexLocker lock(&m_displayMutex);
m_newDisplayText = str;
if (m_pendingRefresh) return;
m_pendingRefresh = true;
lock.release();
QMetaObject::invokeMethod(this, &MainWindow::DisplayInGui_impl);
}
private:
Q_SLOT void DisplayInGui_impl() {
if (!m_displayRefreshTimer.isActive())
m_displayRefreshTimer.start(this, m_updatePeriod);
}
};
In a more complex situation you'd likely want to factor out the cross-thread property setting to some "adjunct" class that would perform such operations without the boilerplate.
Upvotes: 1
Reputation: 12929
You could take advantage of the fact that QTimer::singleShot
has an overload which, when called with a zero time interval, allows you to effectively schedule a task to be run on a specified thread during that thread's next idle slot...
void QTimer::singleShot(int msec, const QObject *context, Functor functor);
So your MainWindow::mainFunction
could be something along the lines of...
void MainWindow::mainFunction ()
{
...
std::string output = get_ouput_from_somewhere();
QTimer::singleShot(0, QApplication::instance(),
[output]()
{
display(output);
});
...
}
Upvotes: 0
Reputation: 51920
You must schedule the code that does UI calls to run in the main thread. I use a simple and easy to use wrapper for that:
#include <QApplication>
#include <QtGlobal>
#include <utility>
template<typename F>
void runInMainThread(F&& fun)
{
QObject tmp;
QObject::connect(&tmp, &QObject::destroyed, qApp, std::forward<F>(fun),
Qt::QueuedConnection);
}
You can now run code (using a lambda in this example, but any other callable will work) in the main thread like this:
runInMainThread([] { /* code */ });
In your case:
void display(std::string output)
{
runInMainThread([output = std::move(output)] {
MainWindow* gui = MainWindow::getMainWinPtr();
gui->DisplayInGUI(output);
});
}
Or you can leave display()
as is and instead wrap the calls to it:
runInMainThread([str] { display(std::move(str)); );
The std::move
is just an optimization to avoid another copy of the string since you should not pass the string by reference in this case (it would be a dangling reference once the string object goes out of scope.)
This is not a high performance inter-thread communication mechanism. Every call will result in the construction of a temporary QObject and a temporary signal/slot connection. For periodic UI updates, it's good enough and it allows you to run any code in the main thread without having to manually set up signal/slot connections for the various UI update operations. But for thousands of UI calls per second, it's probably not very efficient.
Upvotes: 1