Qt UI, blocking calls, and reentrancy
I come from a Microsoft world, where because of how COM works with STA apartments, blocking calls such as to WaitOne, or to WCF services, do not prevent re-entrancy. This is because blocking calls does not completely block, but allows for certain types of messages to continue being pumped, including paint, input and any posted messages. The COM message pump implementation does initially enter a wait state, but if any new messages meeting the required filter for MsgWaitForMultipleObjects() are received, it awakens. This is by design for a number of reasons, to prevent undesired blocking on a UI thread being among them. The proper way to perform asynchronous actions like WCF web service calls is either to use asynchronous design patterns, which implement callbacks to inform the caller of completion, or else to perform synchronous web service calls on a worker thread, rather than on the UI thread.
I am wondering if the same concept and recommendation applies to Qt.
In Qt applications you will normally call QApplication::exec() from your main thread in your main() function. This will start the event loop. Usually the application won't return from there, until it terminates. The event loop does all the event processing for you and it will keep the GUI responsive. If you need to react to a certain event, then you will use the "Signals and Slots":http://qt-project.org/doc/qt-4.8/signalsandslots.html concept of Qt: You connect a slot to the signal that you want to react to. Every time the signal is emitted, the slot will be called. Much like a "callback", but with type safety.
If both objects, the sender and receiver, live in the same thread, a "direct" connection will be used by default. This means that the emit will block until the slot has returned. Also the slot will be executed in the context of the thread that has executed the emit. When connecting objects that belong to different threads, then a "queued" connection is used by default. With such connection, the emit returns immediately. The slot will be executed in the context of the thread to which the receiver belongs. Using queued connections requires the thread to which the receiver belongs to execute an event loop, because the event loop will call the slot eventually.
If a slot that was called from the main thread is blocking for too long, the GUI will become unresponsive! So if you want to do an "asynchronous" call, you need to create a background/worker thread with QThread. Update messages or results can be send from the "background" thread to the "main" thread via Signals & Slots. Using a "queued" connection makes it thread-safe, because the slot will always be executed by the "main" thread's event loop, even when the signal was emitted from the background thread.
If you want to wait synchronously for some signal, but keep the GUI responsive, you can create your own "local" event loop. Just connect the signal to your event loop's quit() slot:
Alternatively, you may have a look at "QFuture":http://doc.qt.nokia.com/4.7-snapshot/qtconcurrentrun.html for asynchronous calls!
Hope that helps ;-)
Thanks very much,
If the UI calls a function that absolutely must run to completion before any other UI code is called, so no re-entrancy, what are recommendations to achieve this?
As I mentioned, there is in Microsoft UI technologies potentially issue with re-entrancy even if call a wait primitive such as WaitOne because of how the COM message pump works -- if this call is done on a UI thread.
- UI calls methodA that has networkIO
- Since methodA has networkIO, the thread calls a wait primitive of some type, waiting for response
- Because of how COM message pump works, if a message is sent to the UI, with SendMessage, the message pump will process the message and call UI function, even though still waiting for a response from the network
Well, if you call some function from the main thread (which executes the event loop) and that function blocks, then the GUI will simply freeze until the function returns. This, of course, will make sure that no other function can be called from the GUI until the previous function has returned. Simply because the main thread (event loop) is blocked and won't process any events (e.g. user input). But it obviously is not a good idea to call a "blocking" function directly from the GUI, at least if the call takes longer than few milliseconds. Instead you may create a "background" thread with the help of QThread and call the "blocking" function from that thread. The main thread would simply launch the thread and then continue to do event processing in order to keep the GUI responsive. QThread has a finished() signal which you can use to react when the thread has terminated.
if(!myThread) //already in progress?
m_myThread = new MyThread(<parameters here>);
connect(myThread, SIGNAL(finished()), this, SLOT(operationDone()));
QMessageBox::information(this, tr("Done"), tr("Yeah, it's done!"));
delete myThread; myThread = NULL;
Great, thank you.