Home > Software engineering >  How do you wait for `QtConcurrent::run` to finish without thread blocking?
How do you wait for `QtConcurrent::run` to finish without thread blocking?

Time:07-21

Hey this should be a pretty straightforward question. Simply put:

  • Want to run a function in another thread.
  • Need to wait for the function to finish.
  • Do not want to freeze the thread though while waiting.
  • In other words, I'd like to use an eventloop.

Here is the freezing example:

extern void sneed()
{
    QEventLoop wait;
    wait.exec();
}
int main( int argc, char *argv[] )
{
    QApplication a(argc, argv);
    {
        // this starts a tui
        QConsoleToolkit::s_CursesController.start( QCD::CursesEngine::Engine_Thread_Stdout_Monitor );
    }

    ct_Start( "Sneed" );
    QFuture<void> ff = QtConcurrent::run(sneed);
    ff.waitForFinished(); // This freezes the tui
    ct_Finish( "Chuck" );
}

I tried to use a QEventLoop in the main thread instead of ff.waitForFinished(), but I could not figure out how I could emit a signal when ff was finished, because QFuture isnt a QObject, and has no finished signal that I could bind to:

https://doc.qt.io/qt-6/qfuture.html

I tried passing a QObject via reference to emit a signal from it instead, but couldnt get it to compile.

What am I missing here?

CodePudding user response:

The solution comes from a simple class called QFutureWatcher:

doc.qt.io/qt-5/qfuturewatcher.html

Here is some sample code for running lambda's in a different thread, and receiving its value.

template <class T>
auto asynchronous( auto &&lambda )
{
    QEventLoop wait;
    QFutureWatcher<T> fw;
    fw.setFuture( QtConcurrent::run(lambda) );
    QObject::connect   ( &fw, &QFutureWatcher<T>::finished, &wait, &QEventLoop::quit );
    wait.exec();
    QObject::disconnect( &fw, &QFutureWatcher<T>::finished, &wait, &QEventLoop::quit );
    return fw.result();
}

using the function would look like this:

int n(0);
ct_Start(n); // 0 - Running in main thread
n = asynchronous<int>([&n](){
    // Running in background thread.
    // Mainthread is waiting for this lambda to finish
    // but mainthread is not locked. 
    // User Interface will still function.
    for ( int i = 0; i < 100000; i   ){
        ct_Debug(n  ); 
    };
    return n;
});
ct_Finish(n); // 100000

Note: ct_Debug ct_Start ct_Finish are not found in the Qt framework. They are debugging macros for a TUI.

CodePudding user response:

I think you misunderstood my comment. This really is just a follow-up comment. It's not a real answer because I'm not a Qt expert, but I posted it as an "answer" so that I could include a code example.

The question that I tried to ask in my earlier comment was, why can't you do this?

extern void sneed() {
    ...
}

void sneed_wrapper() {
    sneed();
    ...emit a signal...
}

int main( int argc, char *argv[] )
{
    ...
    ct_Start( "Sneed" );
    QFuture<void> ff = QtConcurrent::run(sneed_wrapper);
    ...
}

You said that you could solve your problem if the thread that runs sneed() would send a signal that would alert the main event loop when the thread ends.

OK, so why not have the thread run sneed_wrapper() as shown above? sneed_wrapper() runs sneed() and then when sneed() is finished, it can do whatever it takes in Qt* to send the signal that will alert the main loop.


* Did I mention that I don't know Qt?

  • Related