PDA

View Full Version : QtJambi: QTimer can only be used with threads started with QThread



further
25th May 2008, 14:17
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 :crying:: 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.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);
}
}





Handler:



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();
}
}

wysota
25th May 2008, 15:15
Can't you derive your thread from QThread and call QThread::exec() to start the event loop? Then you'll be able to get rid of your while() loop and base everything on signals and slots.

further
25th May 2008, 15:58
Thanks Wysota. I've a problem with subclassing QThread in QtJambi. It seems the class is final, if I refer to http://java.trunat.fr/qtjambi/com/trolltech/qt/QThread.html though Trolltech documentation doesn't mention this point (because it just forward to the Qt description, which obviously can be inherited in C++. Similarly there is no exec method in the QtJambi version of QThread (cf. same doc above). Java programmers are a little bit more alone than C++ colleagues ;).

The fact is that Eclipse doesn't allow me to subclass QThread in Java. Anyway there is a constructor which return a QThread instance from a Runnable object. I think I have already tried this method too.

I'll check again. I just don't understand the meaning of the error message. Thanks for the suggestion. Will let you know the outcome.

wysota
25th May 2008, 16:59
From what I see you should not subclass QThread but simply use it.

public QThread(java.lang.Runnable target)

This constructor takes a runnable object that will be executed as the new thread. As for exec() I don't see a solution in the docs... If you have a Qt commercial licence, I suggest contacting Trolltech Support about it.

further
25th May 2008, 17:00
I don't see any other way to create a QThread object than using new QThread(Runnable object).start() like I did. I appears to me that QThread (http://doc.trolltech.com/qtjambi-4.4.0_01/doc/html/index.html?com/trolltech/qt/QThread.html) is a final class in QtJambi and doesn't provide exec(). About getting rid of the loop for signal/slot construct, could you be more specific? Thanks.

further
27th May 2008, 11:48
It seems there is no solution with QtJambi, I've posted a request for support in the Trolltech news group for QtJambi (I've only the open source version), but noone has something to propose so far. Could be a very puzzling limitation of the Java implementation of Qt. Will look if JFace which is Java native could offer a better choice. Thanks anyway for your suggestion.

wysota
27th May 2008, 23:01
Send a question to qtbugs at trolltech.com, they might suggest a solution. Just make sure you submit it as a bug report or a suggestion, not a direct request for help.

further
28th May 2008, 15:27
Many thanks Wysota for contacting Trolltech, this can help many of us. I had a second thought about being too much dependent on the deployment and strategy of QtJambi. If Swing is weak and not so modern, the other Java libraries should be sufficiently powerful, so I'm now rewriting my application using Sun libraries for non-UI needs.

tobey_maguire
27th March 2013, 11:51
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 :crying:: 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.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);
}
}





Handler:



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();
}
}

thank you for sharing the code with us...!!!
I was searching for a few days for this little help...!!!