Alternative 1: Server Push
This documentation is for an older version of ZK. For the latest one, please click here.
Server push is so-called reverse-Ajax which allows the server to send content to the client actively. With the help of server push, you could send or update the content to the client in the working thread when your predefined condition is satisfied. To use server push is simple,and it requires only three steps as follows,
- Enable server push for the desktop
Invoke Desktop.enableServerPush(boolean bool) to enable server push - Passing components required to be update into the working thread
- Invoke the working thread in the desktop
Note: You need to install zkex.jar or zkmax.jar to have the server push, unless you have your own implementation of ServerPush.
Let』s take a look at a real example below. If you want to update the number of client using server push, first of all, you have to enable server push for the desktop and then to invoke the thread as follows,
<window title="Demo of Server Push" border="normal">
<zscript>
import test.WorkingThread;
WorkingThread thread;
void startServerpush(){
//enable server push
desktop.enableServerPush(true);
//invoke working thread and passing required component as parameter (in this case we are passing the desktop)
thread= new WorkingThread(desktop);
thread.start();
}
</zscript>
<vbox>
<button label="Start Server Push" onClick="startServerpush()" />
</vbox>
</window>
Activation
One thing to notice is that the problem of synchronization which happens when a desktop is accessed by multiple threads at the same time Thus, before accessing the desktop, you have to invoke Executions.activate(Desktop desktop) to get full control of the desktop to avoid this problem, and then release the control of the desktop by invoking Executions.deactivate(Desktop desktop) after the thread finishing its job as follows,
package test;
import org.zkoss.lang.Threads;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.util.Clients;
public class WorkingThread extends Thread {
private final Desktop _desktop;
public WorkingThread(Desktop desktop) {
_desktop = desktop;
}
public void run() {
try {
if (_desktop.isServerPushEnabled()) {
for (int i = 0; i < 5; i++) {
Executions.activate(_desktop);
Clients.showBusy("Executing " + (i + 1) + " of 5", true);
Executions.deactivate(_desktop);
// Simulate a long opetation
Threads.sleep(2000);
}
Executions.activate(_desktop);
Clients.showBusy(null, false);
Executions.deactivate(_desktop);
}
} catch (InterruptedException ex) {
} finally {
_desktop.enableServerPush(false);
}
}
}
The result will be something like this:
ERROR: Width and Height not set
Behind the Scene
client-polling
The mechanism of server push is implemented using client-polling technique which the client will query the server repetitively to invoke the working thread to do its job, and the frequency of query could be adjusted manually by invoking Executions.setDelay(int min, int max, int factor).
- min, the minimal delay to poll the server for any pending server-push threads.
- max, the maximum delay to poll the server for any pending server-push threads.
- factor, The real delay is the processing time multiplies the delay factor.
Last, one thing to notice is that the frequency will be adjusted automatically depending on the loading of the server
Comet
Since ZK 3.5, in zkmax.jar we provide another mechanism to implement server push. It's called Comet. It's implemented by holding one connection open for real-time events. In particular, the HTTP 1.1 specification states that a browser should not have more than 2 simultaneous connections with a web server.
Alternative 2: Thread Suspend and Resume
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) or notifyAll(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.