长操作对于同一个桌面事件是被顺序处理的。换个说法,一个事件的处理程序将可以阻塞所有的后续处
理程序。一个长时间被阻塞的请求可能是不可接受的(the time blocking user’s requests might
not be acceptable),如果一个事件的处理将花费大量的时间。象桌面应用,你需要创建一个专用于工
作这种长时间处理的工作线程来减少阻塞时间。
限制于http协议,我们必须符合以下的规则。
1. 在创建了工作线程后,使用wait方法来挂起事件处理进程本身。
2. 因为工作线程不是一个事件监听器,所以它不能进入任何组件(除非组件不属于任何桌面)。因此
,在开始工作线程前你可能需要手工通过一些必要的信息。
3. 然后,工作线程可以crush the information和创建组件(若必要的话)。只是不把任何组件关联
到任何桌面。
4. 在工作线程结束之后,在工作线程中使用类org.zkoss.zk.ui.Executions notify(Desktop
desktop,Object flag)或者notifyAll(Desktop desktop,Object flag)方法来恢复事件处理进程。
5. 直到从客户另一个事件被发送过来,恢复的事件处理进程才会执行。为了强制发送一个事件,你可
以使用timer组件(org.zkoss.zul.Timer)来触发一个事件a moment later or periodically.这个
timer的时间监听器不做任何事情。
例子:工作线程异步的创建标签
假设我们想异步的创建标签。当然,这有点象用牛刀来杀鸡了,但是我们可以用sophisticated one 来
替换这个任务。
//Working Thread
Package test;
Public class WorkingThread extends Thread
{
private static int _cnt;
private Desktop _desktop;
private Label )label;
private final Object _mutex = new Integer(0);
public static final Label asyncCreate(Desktop desktop)
throws InterruptedException{
final WorkingThread worker = new WorkingThread(desktop);
synchronized(worker._mutex)
{
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)
{
Executions.notify(_desktop,_mutex);
}
}
}
然后,我们有一个ZUML页面在一个事件监听器里来调用这个工作线程,如在onClick中
timer.start();
Label label = test.WorkingThread.asyncCreate(desktop);
main.appendChild(label);
timer.stop();
注意我们需要使用timer来真正恢复被挂起的事件监听器(onclick)。这看起来是多余的,但是归因于
http的限制:为了保持web页面在浏览器中的alive,当事件处理进程被挂起的时候我们必须返回回应。
然后,工作线程完成了工作,唤醒了事件监听器,http的请求已经gone了。因此,我们需要”piggyback
”这个结果,这就是timer被使用的原因。
更准确的来说,当工作进程唤醒了事件监听器,ZK只是把它加到一个等待队列中。当另一个http请求到
达的时候,监听器才真正的恢复。(在上面的例子中,是onTimer事件)
在这个简单里例子中,我们对onTimer事件什么都没做。对于一个sophisticated应用,你可以使用它来
返回处理的状态。
另一个事例:没有挂起和恢复
没有挂起和恢复来执行一个长时间的操作是可能的。当同步代码对于调试来说太复杂的情况下是有用的
。
主意很简单。工作进程在一个临时空间里保存结果,然后onTimer事件监听器将结果弹出到桌面。
//WorkingThread2
package test;
public class WorkingThread2 extends Thread
{
private static int _cnt;
private final Desktop _desktop;
private final List _result;
public WorkThread2(Desktop desktop,List result)
{
_desktop = desktop;
_result=result;
}
public void run(){
_result.add(new Label("Execute "+ ++_cnt));
}
}
然后,在onTimer事件监听器上面追加labels
int numpending = 0;
List result=Collections.synchronizedList(new LinkedList());
++numpending;
timer.start()
new test.workingthread2(desktop,result).start();
while(!result.isEmpty())
{
main.appendChild(result.remove(0));
--numpending;
}
if(numpending==0)time.stop();
初始和清除事件处理线程
在处理每个事件之前初始化
一个事件监听器是在一个事件处理线程中执行的。有时,你不得不在处理所有事件之前初始该线程。
一个典型的例子是初始认证所使用的线程。一些j2ee或者web容器在thread local storage中存储着认证
信息,因此它们可以在需要时自动进行重复认证。
为了进行事件处理线程的初始化,你需要在web-inf/zk.xml文件注册一个继承自
org.zkoss.zk.ui.event.EventThreadInit接口的类。
一旦进行了注册,在开始一个事件处理线程之前,在主线程中一个指定类的实例就被创建了。然后在处
理其他事情之前,该实例的init方法在事件处理线程的上下文中被调用了。
注意那个构造体和init方法是在不同的线程中被调用的,因此开发者可以从一个线程获得线程独立的数
据发送到另一个线程。
下面是jboss的认证机制的例子。在这个例子中,我们在构造体中获得储藏在servlet线程中的信息。然
后,我们在init方法调用的时候初始事件处理线程。
import java.security.Principal;
import org.jboss.security.SecurityAssociation;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventThreadInit;
public class JBossEventThreadInit implements EventThreadInit
{
private final Principal _principal;
private final Object _credential;
public JBossEventThreadInit()
{
_principal=SecurityAssociation.getPrincipal();
_credential=SecurityAssociation.getCredential();
}
public void init(Component comp,Event evt)
{
SecurityAssociation.setPrincipal(_principal);
SecurityAssociation.setCredential(_credential);
}
}
然后在web-inf/zk,xml中,如下进行注册:
JBossEventThreadInit
在处理完每个事件后清除
同样的,你可能不得不在处理完一个事件后清除一个事件处理进程。
典型的例子是关闭一个transaction,如果它没有被适当的关闭。
为了清除一个事件处理线程,你需要注册一个实现org.zkoss.zk.ui.event.EventThreadCleanup接口的
监听类,然后在web-inf/zk.xml中注册。
my.MyEventThreadCleanup