Hi, I have been trying to solve this problem for while now, but failed: I want to perform long operations (multiple http get) in a separate thread, to not block the UI.
While the operations worked when done in the main thread, as soon as I move them in a separate thread, they stopped being triggered. This was the
initial problem that I wanted to submit for your review.
As the code is quite long with multiple classes, I designed a smaller piece of code to post here (still not so small, so that you can have a good view of my technique, which is obvioulsy wrong somewhere).
Unfortunately, when I simulated the http requests with timers, I got
an additional problem with timers
: the message "
QObject::startTimer: QTimer can only be used with threads started with QThread" is displayed twice on the console.
I start with this second problem, because I feel it is something critical to understand before going further. Then I will modify my exemple to ask for the initial problem linked probably to signals.
I believe the timer problem is linked to the lack of event loop due to the way I create the additional thread. I searched hard on the topic, but now give up and need to ask someone clever. I believe the solution for QtJambi may be similar but not identical to the one for C++.
I apologize for the length of the code. I'd be grateful if you could comment my code and provide an advice in this complex (for me) combination of threads and signals.
Thanks by advance.
I have two classes: a main class that post requests, and the handler which processes requests and sends a signal to the posting object at completion of each request. The handler is supposed to self-destroy when all requests have been answered.
Main class:
package test;
import com.
trolltech.
qt.
gui.
QWidget;
/**
* Creates a UI with two check boxes representing status of operations.
* Request two "long operations" to be performed using a special handler.
* Updates the check boxes according to the completion status of the
* operation. Is non blocking.
*/
public class Test extends
QWidget { // static main
public static void main(String[] args) {
new Test().show();
}
// to display the status of each requested operation.
/**
* Constructor
*/
public Test(){
// construct some UI with two checkboxes
checkBox1
= new QCheckBox("Op 1 completed",
this);
checkBox2
= new QCheckBox("Op 2 completed",
this);
layout().addWidget(checkBox1);
layout().addWidget(checkBox2);
// create a long operation handler and connect its signal
LongOpHandler op = new LongOpHandler();
op.opCompleted.connect(this, "opCompleted(int)");
// request two "long operations"
op.enqueue(1);
op.enqueue(2);
}
/**
* Slot for completion of an operation
* @param id the number of the operation, i.e. 1 or 2
*/
public void opCompleted(int id) {
// just update the status of the operations
if (id == 1) checkBox1.setChecked(true); else checkBox2.setChecked(true);
}
}
package test;
import com.trolltech.qt.gui.QApplication;
import com.trolltech.qt.gui.QCheckBox;
import com.trolltech.qt.gui.QVBoxLayout;
import com.trolltech.qt.gui.QWidget;
/**
* Creates a UI with two check boxes representing status of operations.
* Request two "long operations" to be performed using a special handler.
* Updates the check boxes according to the completion status of the
* operation. Is non blocking.
*/
public class Test extends QWidget {
// static main
public static void main(String[] args) {
QApplication.initialize(args);
new Test().show();
QApplication.exec();
}
// to display the status of each requested operation.
private QCheckBox checkBox1;
private QCheckBox checkBox2;
/**
* Constructor
*/
public Test(){
// construct some UI with two checkboxes
checkBox1 = new QCheckBox("Op 1 completed", this);
checkBox2 = new QCheckBox("Op 2 completed", this);
setLayout(new QVBoxLayout());
layout().addWidget(checkBox1);
layout().addWidget(checkBox2);
// create a long operation handler and connect its signal
LongOpHandler op = new LongOpHandler();
op.opCompleted.connect(this, "opCompleted(int)");
// request two "long operations"
op.enqueue(1);
op.enqueue(2);
}
/**
* Slot for completion of an operation
* @param id the number of the operation, i.e. 1 or 2
*/
public void opCompleted(int id) {
// just update the status of the operations
if (id == 1) checkBox1.setChecked(true); else checkBox2.setChecked(true);
}
}
To copy to clipboard, switch view to plain text mode
Handler:
package test;
import java.util.Random;
import com.trolltech.qt.QSignalEmitter;
import com.
trolltech.
qt.
core.
QObject;
import com.
trolltech.
qt.
core.
QTimer;
/**
* Handler for long operations. Allows operations queueing
* and emit a signal when all operations are completed.
* Uses its own thread to not block the application.
*/
public class LongOpHandler extends
QObject implements Runnable
{ public QSignalEmitter.Signal1<Integer> opCompleted = new QSignalEmitter.Signal1<Integer>();
private boolean allDone = false; // flag for all operations completed
private Object timer1 = null; // reference to timer1
private boolean op1Requested = false; // simulate queueing of operation 1
private boolean op2Requested = false; // simulate queueing of operation 2
private boolean op1Done = false; // flag for completion of operation 1
private boolean op2Done = false; // flag for completion of operation 2
/**
* Starts a new thread so that the requester is not blocked
* while requested operations are performed
*/
public LongOpHandler() {
// rename current thread for convenience
thread().setName("Thread-for-UI");
// start a new thread to execute run(), give it a name for convenience
new QThread(this,
"Thread-for-operations").
start();
}
/**
* Simulate the queueing of an operation.
* @param opId the number of the operation to queue: 1 or 2
*/
public synchronized void enqueue(int opId) {
// queue the requested operation, it will be started within run()
if (opId == 1) op1Requested = true; else op2Requested = true;
// wake up the Thread-for-operations in case it is executing wait()
notifyAll();
}
/**
* Method executed in a separate thread to avoid blocking of the main
* thread by long operations. Operations here are simulated by timers.
* Queued operations are dequeued and executed. When all operations
* have been executed, run() is exited. wait() is call when there is
* nothing to do. Queueing and completion of operation must wake up the
* separate thread executing wait().
*/
public synchronized void run() {
// simulated operations
// store reference to timer1 at instance level to allow access to its value
this.timer1 = timer1;
// connect signals to slots
timer1.timeout.connect(this, "done()");
timer2.timeout.connect(this, "done()");
// loop to monitor new requests and completion of all requests
while (!allDone) {
// look for queued requests
if (op1Requested) {
timer1.setSingleShot(true);
timer1.setInterval(new Random().nextInt(500) + 500);
// QTimer.start() triggers:
// "QObject::startTimer: QTimer can only be used with threads started with QThread"
timer1.start();
op1Requested = false;
op1Done = false;
}
if (op2Requested) {
timer2.setSingleShot(true);
timer2.setInterval(new Random().nextInt(500) + 500);
// QTimer.start() triggers:
// "QObject::startTimer: QTimer can only be used with threads started with QThread"
timer2.start();
op2Requested = false;
op2Done = false;
}
// test if all operations are completed
// for the purpose of this test, "all" means timers 1 and 2 timed out
allDone = op1Done && op2Done;
// wait for something to do
while (!allDone) {
try {wait();} catch (InterruptedException e) {}
// update allDone flag
allDone = op1Done && op2Done;
}
}
// operations now completed, resources created by Thread-for-operations
// will be destroyed after exiting run()
}
@SuppressWarnings("unused")
private synchronized void done() {
// determine which operation has completed
int opId = (signalSender() == timer1) ? 1 : 2;
// inform the requester of the completion
opCompleted.emit(opId);
// record the completion
if (opId == 1) op1Done = true; else op2Done = true;
// release Thread-for-operations if it is waiting
notifyAll();
}
}
package test;
import java.util.Random;
import com.trolltech.qt.QSignalEmitter;
import com.trolltech.qt.QThread;
import com.trolltech.qt.core.QObject;
import com.trolltech.qt.core.QTimer;
/**
* Handler for long operations. Allows operations queueing
* and emit a signal when all operations are completed.
* Uses its own thread to not block the application.
*/
public class LongOpHandler extends QObject implements Runnable {
public QSignalEmitter.Signal1<Integer> opCompleted = new QSignalEmitter.Signal1<Integer>();
private boolean allDone = false; // flag for all operations completed
private Object timer1 = null; // reference to timer1
private boolean op1Requested = false; // simulate queueing of operation 1
private boolean op2Requested = false; // simulate queueing of operation 2
private boolean op1Done = false; // flag for completion of operation 1
private boolean op2Done = false; // flag for completion of operation 2
/**
* Starts a new thread so that the requester is not blocked
* while requested operations are performed
*/
public LongOpHandler() {
// rename current thread for convenience
thread().setName("Thread-for-UI");
// start a new thread to execute run(), give it a name for convenience
new QThread(this, "Thread-for-operations").start();
}
/**
* Simulate the queueing of an operation.
* @param opId the number of the operation to queue: 1 or 2
*/
public synchronized void enqueue(int opId) {
// queue the requested operation, it will be started within run()
if (opId == 1) op1Requested = true; else op2Requested = true;
// wake up the Thread-for-operations in case it is executing wait()
notifyAll();
}
/**
* Method executed in a separate thread to avoid blocking of the main
* thread by long operations. Operations here are simulated by timers.
* Queued operations are dequeued and executed. When all operations
* have been executed, run() is exited. wait() is call when there is
* nothing to do. Queueing and completion of operation must wake up the
* separate thread executing wait().
*/
public synchronized void run() {
// simulated operations
QTimer timer1 = new QTimer();
QTimer timer2 = new QTimer();
// store reference to timer1 at instance level to allow access to its value
this.timer1 = timer1;
// connect signals to slots
timer1.timeout.connect(this, "done()");
timer2.timeout.connect(this, "done()");
// loop to monitor new requests and completion of all requests
while (!allDone) {
// look for queued requests
if (op1Requested) {
timer1.setSingleShot(true);
timer1.setInterval(new Random().nextInt(500) + 500);
// QTimer.start() triggers:
// "QObject::startTimer: QTimer can only be used with threads started with QThread"
timer1.start();
op1Requested = false;
op1Done = false;
}
if (op2Requested) {
timer2.setSingleShot(true);
timer2.setInterval(new Random().nextInt(500) + 500);
// QTimer.start() triggers:
// "QObject::startTimer: QTimer can only be used with threads started with QThread"
timer2.start();
op2Requested = false;
op2Done = false;
}
// test if all operations are completed
// for the purpose of this test, "all" means timers 1 and 2 timed out
allDone = op1Done && op2Done;
// wait for something to do
while (!allDone) {
try {wait();} catch (InterruptedException e) {}
// update allDone flag
allDone = op1Done && op2Done;
}
}
// operations now completed, resources created by Thread-for-operations
// will be destroyed after exiting run()
}
@SuppressWarnings("unused")
private synchronized void done() {
// determine which operation has completed
int opId = (signalSender() == timer1) ? 1 : 2;
// inform the requester of the completion
opCompleted.emit(opId);
// record the completion
if (opId == 1) op1Done = true; else op2Done = true;
// release Thread-for-operations if it is waiting
notifyAll();
}
}
To copy to clipboard, switch view to plain text mode
Bookmarks