How can I invoke functions on QObjects from another thread?
QObjects are owned by a thread and most QObjects should only be used from within that thread. When QObjects are illegally accessed from outside their thread an exception will be thrown, containing the message "QObject used from outside its own thread".
Answer:
There are a three basic methods for calling a function from one thread on a QObject in another thread.
- The most basic operation is to post an event to the object in the other thread. The event loop in the target objects thread will then deliver the event to the target object. This method relies on the target object knowing how to respond to the event, and is therefore a bit awkward to use.
- A simpler approach is to make use of QCoreApplication.invokeLater(). This function is implemented in terms of the first method and only works for objects in the GUI thread, but this is where most QObjects reside, so it is quite functional. As illustrated in the InvokePusle class below we instantiate a runnable that will be executed in the GUI thread.
- The last approach is to make use of Qt queued connections. This is also implemented in terms of the first method, and works for any target object that has an event loop running in the thread that owns it. One can either specify a queued connection by passing the parameter Qt.ConnectionType.QueuedConnection to the connect statement or use Qt.ConnectionType.AutoConnection, the default, which decides at runtime how the slot should be called. In the code example below we declare and emit the signal in the SignalPulse class and connect it as part of the setup code in the main() function.
import com.trolltech.qt.*;
import com.trolltech.qt.core.*;
import com.trolltech.qt.gui.*;
public class QObjectFromThread {
// This runnable will call the "invokeLater()" function to update
// the labels text every second or so..
private static class InvokePulse implements Runnable {
private QLabel label;
public InvokePulse(QLabel label) {
this.label = label;
}
public void run() {
final QTime t = new QTime();
t.start();
while (true) {
try { Thread.sleep(1000); } catch (Exception e) { };
QApplication.invokeLater(new Runnable() {
public void run() {
label.setText("Thread(" + Thread.currentThread().getId()
+ "), elapsed: " + t.elapsed());
}
});
}
}
}
// This runnable will emit the signal "pulse" every second or so
// with the number of milliseconds since it started...
private static class SignalPulse extends QSignalEmitter implements Runnable {
public Signal1<String> pulse = new Signal1<String>();
public void run() {
QTime t = new QTime();
t.start();
while (true) {
try { Thread.sleep(1000); } catch (Exception e) { };
pulse.emit("Thread(" + Thread.currentThread().getId()
+ "), elapsed: " + t.elapsed());
}
}
}
public static void main(String args[]) {
QApplication.initialize(args);
QWidget window = new QWidget();
QLabel a = new QLabel("n/a");
QLabel b = new QLabel("n/a");
QVBoxLayout layout = new QVBoxLayout(window);
layout.addWidget(a);
layout.addWidget(b);
window.show();
Thread ta = new Thread(new InvokePulse(a));
ta.setDaemon(true);
ta.start();
SignalPulse pulse = new SignalPulse();
pulse.pulse.connect(b, "setText(String)");
Thread tb = new Thread(pulse);
tb.setDaemon(true);
tb.start();
QApplication.exec();
}
}