0

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?

Anon
  • 2,267
  • 3
  • 34
  • 51
  • You're missing a `QMutex`, a `QWaitCondition`, and a `bool` flag? Do you know what they are and how to use them? – Sam Varshavchik Jul 19 '22 at 10:56
  • 1
    Re, "I could not figure out how I could emit a signal when..." How about, instead of running `sneed` in a background thread, you run a function that calls `sneed` and then emits a signal? – Solomon Slow Jul 19 '22 at 14:10
  • @SamVarshavchik A QMutex locks and blocks the thread. so I dont see what you would be getting at here. An event loop on the other hand wouldnt. – Anon Jul 20 '22 at 06:17
  • @SolomonSlow that wont work, because the signal needs to be emitted when the background thread finishes. – Anon Jul 20 '22 at 09:28
  • @Anon, I don't understand. How is "when the background thread finishes" any different from, "when `sneed()` returns?" – Solomon Slow Jul 20 '22 at 12:47
  • @SolomonSlow Mmmm maybe I got confused by your suggestion. The `sneed()` function is processor intensivee irl, and so if I ran it on the main thread, it would freeze the TUI. So I need run run the function, and wait for it to finish. But if I wait for it to finish using a mutex, that will freeze the thread. – Anon Jul 21 '22 at 06:15

2 Answers2

0

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.

Anon
  • 2,267
  • 3
  • 34
  • 51
0

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?




OP here. To elaborate, you could make your example work using global QObject. [So by this metric, this is also a potential answer]:


SomeObject o;
extern void sneed() {
    ...
    emit o.finished();
}

int main( int argc, char *argv[] )
{
    ...
    ct_Start( "Sneed" );
    QEventLoop wait;
    QFuture<void> ff = QtConcurrent::run(sneed);
    QObject::connect( &o, &SomeObject::finished, &wait, &QEventLoop::quit );
    wait.exec();
    ct_Finish( "Sneed" );
}

You may ask, why not pass a local QObject by reference or by pointer? I tried that, but was not able to get the program to compile.

Anon
  • 2,267
  • 3
  • 34
  • 51
Solomon Slow
  • 25,130
  • 5
  • 37
  • 57