1
\$\begingroup\$

The idea is that, in my application I have 5 routines named long_process_1, long_process_2, long_process_3, long_process_4, long_process_5. These are each long calculations that would hang the GUI and can only be called in that order. When each result is ready, user input is required about the result. So we call ask_user_1, ask_user_2, ask_user_3, ask_user_4, ask_user_5 which must obviously run in the main GUI thread. Thus the whole thing cannot be run in a separate thread in its entirety. Here's how I did it:

void MainWindow::on_button_click() // starts with a button click
{
    QEventLoop eventLoop;
    QFutureWatcher<int> watch;

    // Watcher intercepts eventLoop when the computation is finished
    connect(&watch, &decltype(watch)::finished, &eventLoop, &decltype(eventLoop)::quit);

    auto const res1 = QtConcurrent::run(long_process_1);
    watch.setFuture(res1);
    eventLoop.exec(); // Handles GUI during computation
    auto const user_in1 = ask_user_1(res1);

    auto const res2 = QtConcurrent::run(long_process_2, user_in1);
    watch.setFuture(res2);
    eventLoop.exec();
    auto const user_in2 = ask_user_2(res2);

    auto const res3 = QtConcurrent::run(long_process_3, user_in2);
    watch.setFuture(res3);
    eventLoop.exec();
    auto const user_in3 = ask_user_3(res3);

    auto const res4 = QtConcurrent::run(long_process_4, user_in3);
    watch.setFuture(res4);
    eventLoop.exec();
    auto const user_in4 = ask_user_4(res4);

    auto res5 = QtConcurrent::run(long_process_5, user_in4);
    watch.setFuture(res5);
    eventLoop.exec();
    (void)ask_user_5(res5); // result is not used
}

This works alright. I find this a lot simpler than running the whole thing through "on-finished" signal-slots. It also simplifies ownership of local variables/function results as they are all in a single function. The long routines are clearly decoupled from the function calling them. It seems a bit odd overall though. I got the idea from very old Qt docs. All comments are welcome.

Edit:

Coroutines might be helpful. They are not supported in Qt yet, but this library is worth looking into.

\$\endgroup\$
10
  • 1
    \$\begingroup\$ Those are terrible variable names - they should really reflect the actions, rather than the sequencing (and that will save you from pain when you realise you need a step between 3a and 3b....) \$\endgroup\$ Commented Nov 11, 2022 at 13:52
  • \$\begingroup\$ @TobySpeight this is an implementation of the idea. Within this context, the variable names make sense. Is it worth closing this question over variable names? \$\endgroup\$ Commented Nov 11, 2022 at 14:41
  • 1
    \$\begingroup\$ It looks like example/obfuscated code. I'd like to be able to answer, but there really isn't very much reviewable here, especially since your long_process_x variables and ask_user_x functions are completely missing. \$\endgroup\$ Commented Nov 11, 2022 at 15:36
  • \$\begingroup\$ @TobySpeight I want the function design to be reviewed. Would you say that is off-topic on this website? I can delete this 2-years-old question if it is. \$\endgroup\$ Commented Nov 11, 2022 at 15:37
  • 2
    \$\begingroup\$ "Is it worth closing this question over variable names" It's not just the variable names. I think this answer explains it pretty well. "You cannot change the design without heavily impacting the code." We can take a look at the design as part of the rest of the code, but without sufficient code there's nothing to look at. Based on what you currently show us, I'd say it's a poor design. Part of that could be fixed with additional functions or using a container type, but I can't say for sure without seeing what it's doing at the moment. \$\endgroup\$
    – Mast
    Commented Nov 12, 2022 at 14:28

1 Answer 1

2
\$\begingroup\$

Running non-gui related stuffs using the main event-dispatch(gui-thread) is kind of anti-gui practice.

If first process should be called, and then second process, so it's basically a long synchronous calls (with user prompt between each)

Probably(good practice) is run the first long process using a thread(usually out of event-dispatch/gui-thread).

Meanwhile, the long process could inform the GUI about the process by sending progress events(assuming gui works as a process-progress-listener here).

Now, because the long process is run by a thread, gui is functional, and not freezing, so user could see progress, or control the progress(e.g. abort, etc...)

Once the long thread is finished, ask the gui-thread/event-dispatch for user prompt, and if starting the second long process is a thing, go for it just like first one using a thread.

Overall

Try not to perform excessive non-gui stuffs using gui-thread/event-dispatch. This is actually a good practice.

Try not to perform gui related stuffs(such as updating a progress-bar value/status) using non-gui threads(out of widget/dispatch scope). This may not be valid for all widgets, but usually(possible) any gui change(here like progress-bar change) seems to be ignored.
But actually that out-of-scope thread could not force the main-gui thread to perform a refresh/repaint on target component.

Assuming, the-gui/event-dispatch thread is the guy who sitting next to the widget door, and is waiting for any gui-related requests.
So if a request about updating progress-bar is made correctly by the expected routine, that guy will repaint the target progress-bar since it have to.

But what happens when the request came from out of event-dispatch scope? It's more like getting into the room from windows, rather the door, and the guy next to it.
So technically progress bar will be updated, but might not updated immediately(or at all), since the guy who in charge to keeps ui updated did not realized there is a requests that forces component(s) to get repainted.

\$\endgroup\$
3
  • \$\begingroup\$ I am not sure what I am doing is excessively non-gui in gui thread though. All non-gui stuff are done in other threads. Gui thread simply handles the user-interaction part. Otherwise it gets way too complicated. The gui thread creates a worker thread. This thread returns back with the answer, but can't prompt the user, so somehow has to go ask the gui thread to do it. Gui thread must call a different function which does the second thread creation, which must also do the same thing. Then a third function and so on and so on. \$\endgroup\$ Commented Sep 15, 2020 at 18:10
  • \$\begingroup\$ Could you in a small sample show how you would propose this to be done? \$\endgroup\$ Commented Sep 15, 2020 at 18:11
  • \$\begingroup\$ Not every widget come with same/one threading/dispatching impl. But certainly there must a way in your case to allow the worker non-gui thread callback a gui routine when it's done, lets see what QT provide for that then? (this may help) \$\endgroup\$
    – user230399
    Commented Sep 15, 2020 at 18:55

Not the answer you're looking for? Browse other questions tagged or ask your own question.