1

I am writing a C++ QT5 Widget Desktop Application and I need to run a time consuming operation MainWindow::performLengthyOperation(bool) on a separate thread when a start/stop button is pressed.

This time consuming operation is a rather lengthy method in my MainWindow.h/cpp. The operation to stop the background IO activity takes about 6 seconds and to start it takes about 2 seconds. During the time the start/stop button is pressed, the UI is non responsive. Essentially, in the slot attached to my button click event I need to perform the following logic.

void
MainWindow::on_pushButtonStart_clicked(bool checked)
{
    // temporarily disable the pushbutton 
    // until the lengthy operation completes
    mUI->pushButtonStart->setEnabled(false);

    if (checked) {
        // Start the timer tick callback
        mTickTimer.start(APP_TICK, this);
        mUI->pushButtonStart->setText("starting...");

        // This method needs to somehow run in its own QT thread
        // and when finished, call a slot in this MainWindow to
        // re-enable the pushButtonStart and change the statusBar
        // to indicate "runing..."
        performLengthyOperation(true);
        //mUI->pushButtonStart->setText("Stop")
        //mUI->statusBar->setStyleSheet("color: blue");
        //mUI->statusBar->showMessage("runing...");
    } else { // Stop the protocol threads
        // Stop the subsystem protocol tick timer
        mTickTimer.stop();
        mUI->pushButtonStart->setText("stopping...");
        // This method needs to somehow run in its own QT thread
        // and when finished, call a slot in this MainWindow to
        // re-enable the pushButtonStart and change the statusBar
        // to indicate "ready..."
        performLengthyOperation(false);
        // finally toggle the UI controls
        //mUI->pushButtonStart->setText("Start")
        //mUI->statusBar->setStyleSheet("color: blue");
        //mUI->statusBar->showMessage("ready...");
    }
}

When I was looking for examples on how to do this I came across the following article but I am having trouble adapting it to my scenario as I need to somehow get the MainWindow into the worker so it can access its UI widgets etc and that seems like a bit of overkill.

I am ideally looking for a simple way to asynchronously run a lambda function where I could place these time consuming operations (passing in the mainwindow as a parameter). That would be preferable to using QThreads and moving objects to threads etc. But I don't know enough about the QT framework to know if this is a safe or possible thing to do.

johnco3
  • 2,401
  • 4
  • 35
  • 67
  • Even if you pass in your window, you can't manipulate it from that other thread. You'll need to marshal those operations back to the main thread (although I think Qt can do this with signals/slots). – Steve Jul 17 '17 at 22:49

1 Answers1

2

Using std::async and lambdas:

Add std::future<void> future; as a member to your MainWindow class.

Add slot OnLengthyOperationPerformed with code:

mUI->pushButtonStart->setText("Stop")
mUI->statusBar->setStyleSheet("color: blue");
mUI->statusBar->showMessage("runing...");
mUI->pushButtonStart->setEnabled(true);

Add signal void LengthOperationPerformed(); and connect it with slot in MainWindow constructor. Pass Qt::QueuedConnection as a fifth parameter of connection because you want to call your slot from the main thread.

Then in on_pushButtonStart_clicked method you can write:

future = std::async(std::launch::async, [this] {
  performLengthyOperation(true);
  emit LengthOperationPerformed();
});

The same with the other call. Just add another slot and signal or pass flag to slot.

Long action will run in another thread and when it finishes MainWindow receives signal and re-enables the button.

Tony O
  • 513
  • 4
  • 12
  • Nobody. You don't need value stored in future. – Tony O Jul 18 '17 at 20:13
  • Thanks, using the following connect syntax in the constructor connect(this, SIGNAL(LengthyMethodPerformed(const bool)), this, SLOT(updateStartStopButton(const QString&)), Qt::QueuedConnection);however after doing the connect... I get the qt_message_fatal error with: ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to objects owned by a different thread. Current thread 34e6b60. Receiver '' (of type 'QSerialPort') was created in thread 40c460". – johnco3 Jul 18 '17 at 20:24
  • You can't connect signals and slots with different argument types. I've created an example app with async lambda emiting signal to mainWindow https://pastebin.com/nrzrxJVj You can check it and see what I meant. – Tony O Jul 18 '17 at 20:40
  • I suspect that the cause of the above error is the code I have in the lambda includes closing a serial device - which in turn does some signal/slotting as it shuts down - the error originates in the QSerialPort::~QSerialPort. – johnco3 Jul 18 '17 at 20:48
  • Read this http://doc.qt.io/qt-4.8/threads-qobject.html It explains how you can and how not to use Qt objects in multithread app. Does you serial port was constructed in main thread or in lambda? – Tony O Jul 18 '17 at 20:57
  • looks like I don't need the future as a member of MainWindow - also serial port is initialized in a slot MainWindow::on_pushButtonPortControl_clicked(bool checked) .... where I call mPort = std::make_unique(...), however I hooked up a lambda to handle errors from this serial port which is where I suspect I was getting the error from (moving the serial code outside the future everhting work (thanks!). This is how I setup my serial port to handle errors (basically displaying the error detail on a status bar) – johnco3 Jul 18 '17 at 21:29
  • // handle serial IO errors here mSignalSlotInfo.emplace_back(QtConnect(mPort.get(), &QSerialPort::errorOccurred, [&](const QSerialPort::SerialPortError rSerialError) { mUI->statusBar->setStyleSheet("color: red"); mUI->statusBar->showMessage(mPort->errorString()); })); – johnco3 Jul 18 '17 at 21:30