Alternative 2: Thread Suspend and Resume
This documentation is for an older version of ZK. For the latest one, please click here.
With the help of server push, you don't have to take care about the problem of multi threads. However, if you would like to handle this job by yourself, you have to conform with the following rules due to the limitations of HTTP.
- Use the
wait
method in the Executions class to suspend the event handler itself, after creating a working thread. - Because the working thread is not an event listener, it cannot access any components, unless the components don't belong to any desktop. Thus, you might have to pass necessary information manually before starting the working thread.
- Then, the working thread could crush the information and create components as necessary. Just don't reference any component that belongs to any desktop.
- Use the
notify(Desktop desktop, Object flag)
ornotifyAll(Desktop desktop, Object flag)
method in the Executions class in the working thread to resume the event handler, after the working thread finishes. - The resumed event handler won't be executed immediately until another event is sent from the client. To enforce an event to be sent, you could use a timer component (Timer) to fire an event a moment later or periodically. This event listener for this timer could do nothing or update the progress status.
Example: A Working Thread Generates Labels Asynchronously
Assume we want create a label asynchronously. Of course, it is non-sense to do such a little job by applying multi-threading, but you can replace the task with sophisticated one.
//WorkingThread
package test;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zul.Label;
public class WorkingThread extends Thread {
private static int _cnt;
private Desktop _desktop;
private Label _label;
private final Object _mutex = new Integer(0);
/** Called by thread.zul to create a label asynchronously.
*To create a label, it start a thread, and wait for its completion.
*/
public static final Label asyncCreate(Desktop desktop)
throws InterruptedException {
final WorkingThread worker = new WorkingThread(desktop);
synchronized (worker._mutex) { //to avoid racing
worker.start();
Executions.wait(worker._mutex);
return worker._label;
}
}
public WorkingThread(Desktop desktop) {
_desktop = desktop;
}
public void run() {
_label = new Label("Execute "+ ++_cnt);
synchronized (_mutex) { //to avoid racing
Executions.notify(_desktop, _mutex);
}
}
}
Then, we have a ZUML page to invoke this working thread in an event listener, say onClick
.
<window id="main" title="Working Thread">
<button label="Start Working Thread">
<attribute name="onClick">
timer.start();
Label label = test.WorkingThread.asyncCreate(desktop);
main.appendChild(label);
timer.stop()
</attribute>
</button>
<timer id="timer" running="false" delay="1000" repeats="true"/>
</window>
Notice that we have to use a timer to really resume the suspended the event listener (onClick
). It looks odd, but it is a must due to the HTTP limitation: to keep Web page alive at the browser, we have to return the response when the event processing is suspended. Then, when the working thread completes its job and notifies the even listener, the HTTP request was already gone. Therefore, we need a way to 'piggyback' the result, which the timer is used for.
More precisely, when the working thread notifies the event listener to resume, ZK only adds it to a waiting list. And, the listener is really resumed when another HTTP request arrives (in the above example, it is the onTimer
event)
In this simple example, we do nothing for the onTimer
event. For a sophisticated application, you can use it to send back the progressing status.