22

I'm new to Qt, but need to solve a difficult problem.

I've created a VERY simple GUI that I need to add to an existing C++ application. The problem is, I'm writing just one module that plugs into a larger architecture which restricts my access to the main thread.

My code must reside inside the four following functions: an Init() function, which runs in the main thread. and WorkerStart(), WorkerStep(), and WorkerStop() functions that run in a worker thread.

I coded my QApplication and GUI objects in the Init() function. But of course, calling app.exec() at the end of that function blocks the entire rest of the code. Not workable.

Everything I'm reading says that Qt gui objects can only run in the main thread.

So my question is, how can I set up my gui in the init() function (main thread) and allow it to run by only using the worker thread from then on?

I found this: QApplication In Non-Main Thread

and those solutions gave me some different behavior. In the right direction, but not stable or fully functional. But I dont understand why those are solutions at all if qt gui's can only run in main thread, and these solutions put them in other threads. So thats sending mixed messages on what can and can not run in other threads, and it becomes very confusing.

It seems that adding a gui to an existing C++ program without locking it up in the exec() func should be a fairly common situation so I feel like I'm missing something obvious. Can someone help with how I can solve this?

Much thanks in advance. Phil

Community
  • 1
  • 1
Siddhartha
  • 223
  • 1
  • 2
  • 7
  • `QObject::moveToThread`, but you're definitely fighting against the stream on this one. – Matt Phillips Mar 09 '14 at 23:09
  • Hi Matt. Thanks for the comment. Can you elaborate a bit for me? There's many objects involved, (QApp, QDialog, etc) and a few threads. So what are you saying should be moved where? Move everything to my worker thread? or to a new QThread launched from main? Or leave QApp in main, and move other things to a new thread? If this is really a poor fit for Qt can anyone recommend a different gui toolkit without this restriction? – Siddhartha Mar 09 '14 at 23:37

2 Answers2

28

Most of the time, "main thread" == "GUI thread", so people use those terms interchangeably -- even the official documentation does that. I agree that it's confusing though, because they don't have to be the same.^ The actual rule is this:

GUI classes must only be accessed from the thread which instantiates QApplication/QGuiApplication

With a plugin like yours, here is what you need to do:

  1. Create a new std::thread (NOT a QThread)
  2. Run an init function in that thread. Let it instantiate your QApplication/QGuiApplication and start the event loop
  3. Ensure that all your GUI objects are accessed from that thread only.

Voila, you now have a GUI thread that is not your main thread.


^Note: It is a different story on Mac OS X. Due to restrictions in the Cocoa framework, the main thread MUST be the GUI thread. The steps I outlined above will work on Windows/Linux but not on Mac. For Mac, you need to inject your code into the main thread -- see Kuba Ober's comments below.

JKSH
  • 2,658
  • 15
  • 33
  • 1
    Thank you. This was very helpful in understanding the main!=gui thread issue. So I can create a dedicated separate Qt thread. Unfortunately, there's other plugins in my architecture that also use Qt (OpenCV qt specifically) and there seems to be fighting/blocking when both plugins are invoked. So I'm not outta the woods yet. I'm going to experiment with running a qt event loop that self blocks its own thread intermittently. Hoping that will allow the other qt plugin to play nice (time slicing the two). So, thanks for getting me past the first hurdle. Cheers. – Siddhartha Mar 11 '14 at 15:07
  • 2
    On OS X, it's rather simple to inject code into the main thread: `dispatch_sync(dispatch_get_main_queue(), ^{ /* do stuff */ });` You can also run the Qt's event loop in that thread, since it runs the native event loop. – Kuba hasn't forgotten Monica Apr 10 '14 at 19:54
  • @KubaOber To clarify: Do you mean injecting QApplication::exec() won't block the caller? – JKSH Jul 26 '14 at 00:32
  • 1
    @JKSH It won't block the caller, but it will "block" the GUI thread. But since it will spin the native event loop on the GUI thread, this can be inconsequential. – Kuba hasn't forgotten Monica Jul 26 '14 at 00:37
  • @KubaOber Thanks, I think it's getting clearer. My current understanding is as follows; can you please confirm/correct?: You can asynchronously inject (into the main thread) a function that constructs a QApplication and calls QApplication::exec(). This blocks the original event loop and spins a 2nd one (a bit like running a new QEventLoop in the GUI thread of a 'pure' Qt app). All events for Qt and the original app will now be handled by the 2nd event loop. When QApplication::exec() returns, control will return to the original event loop. – JKSH Jul 27 '14 at 01:28
  • @JKSH That's true, as much as it applies to OS X. – Kuba hasn't forgotten Monica Jul 27 '14 at 13:33
  • @KubaOber I have just stumbled on this question because I have the exact same problem but can't seem to use your suggestion to solve it. I have a plugin that is being deployed as a bundle on OSX. This bundle is loaded by an host app and on the init i'm using dispatch_sync(dispatch_get_main_queue(), ^{ startGui(); }); but when I instantiate the plugin the app blocks. Using a pthread to call startGui function, the Qt/Qml window opens but the app gets blocked. startGui() declares an QGuiApplication and QQmlApplication engine and calls exec. Any ideas of what I'm might doing wrong? Thx – Nuno Santos Nov 02 '14 at 19:52
  • @NunoSantos I'll have to check that. On OS X the GUI will not work in any but the main thread, so there's no point in trying the pthread route. – Kuba hasn't forgotten Monica Nov 02 '14 at 23:09
  • @KubaOber in the meanwhile I was able to create an QGuiApplication and QQmlApplicationEngine on my plugin and it is responding to UI interaction and communicating with the class that instantiated via signal/slots with no problems. I haven't been faced with limitations yet. – Nuno Santos Nov 03 '14 at 15:58
  • [The documentation](http://qt-project.org/doc/qt-5/threads-qobject.html) says: "They can only be used from the main thread. As noted earlier, QCoreApplication::exec() must also be called from that thread.". So this doesn't seem accurate (apart from not working). – BartoszKP Nov 12 '14 at 15:52
  • @BartoszKP What exactly isn't working? Can you post the code that you tried (in a new question)? Anyway, as implied in my answer, the documentation is inaccurate and needs updating. See http://comments.gmane.org/gmane.comp.lib.qt.devel/14634 -- Thiago Maciera is the official maintainer for Qt Core, and Olivier Goffart is a long-time contributor to Qt Core. Both of them proposed this technique. – JKSH Nov 14 '14 at 06:11
  • @JKSH Interesting. I've stumbled on this while writing [this answer](http://stackoverflow.com/a/26890922/2642204). The difference probably is because of using PySide. The code **almost** works, producing only a warning, when creating `QApplication` within the other thread - but crashes at exit. If you're interested in diagnosing this I can create a question with a minimal example. – BartoszKP Nov 14 '14 at 13:14
  • @JKSH I took the liberty of editing your answer, so I could retract my downvote - I will have to verify it with C++ again, as clearly Python Qt libraries behave not exactly in the same manner. – BartoszKP Nov 14 '14 at 13:16
  • @BartoszKP Thanks, your edit does highlight key terms/phrases better. I'm afraid I don't know Python, but here's my guess: Make sure you fully shut down the QApplication thread first before you stop the main thread and quit the program. (If that's not it, I think it's worth posting a question -- other users might know how to fix the crash) – JKSH Nov 14 '14 at 14:38
  • It's pleasing to see that this works, despite the message. Given that all my QObject access is in the same thread as QApplication (though not the "main" thread), is there any hope for suppressing the warning? – Jeff Trull Aug 03 '18 at 05:06
  • 1
    @JeffTrull Qt doesn't actually know which thread is the "true" main thread; Qt simply remembers the very first thread that uses a QObject. So, if you ensure that QApplication is the very first Qt class that's instantiated in your program, you won't see that warning no matter which thread you're in. (In other words: Don't use any Qt class before creating QApplication. Don't even call any Qt static methods!) – JKSH Aug 03 '18 at 07:17
  • Well, I don't see where I do it, then :) Is there any way to debug? – Jeff Trull Aug 04 '18 at 05:41
  • @JeffTrull It's best to ask a new question and provide details. Post the link here and I'll have a look. – JKSH Aug 04 '18 at 09:43
  • 1
    I'll add, for anyone who comes across this question in the future, that if you're using PyQt the mere process of doing an "import" appears to trigger this issue. Workaround: do even the imports in your GUI thread - avoiding construction of QObjects is not enough. – Jeff Trull Aug 05 '18 at 06:19
0

A good solution is found in: git@github.com:midjji/convenient_multithreaded_qt_gui.git

then it's just e.g.

run_in_gui_thread(new RunEventImpl([](){
        QMainWindow* window=new QMainWindow();
        window->show();
    }));

callable from any thread, at any time, while taking care of setting things up for you in the bg.

Note, this also takes care of creating a QApplication and executing it in a thread. But also works if you have already done so somewhere already.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
midjji
  • 394
  • 3
  • 9