高级ZK:异步UI更新和后台处理–第2部分
介紹
在第1部分中,我展示了如何在ZK應(yīng)用程序中使用服務(wù)器推送和線程來執(zhí)行后臺任務(wù)。 但是,這個簡單的示例具有一個重大缺陷,這使其對于實際應(yīng)用程序而言是一種不好的方法:它為每個后臺任務(wù)啟動了一個新線程。
JDK5引入了ExecutorService類,該類抽象了線程詳細(xì)信息,并為我們提供了一個不錯的接口,可用于提交任務(wù)以進(jìn)行后臺處理。
在這篇博客文章中,我將描述創(chuàng)建ZK應(yīng)用程序的最重要部分,該應(yīng)用程序包含一個采用字符串并以大寫形式返回的后臺任務(wù)。 完整的示例項目可在Github上找到:
https://github.com/Gekkio/blog/tree/master/2012/10/async-zk-part-2
1.創(chuàng)建一個ExecutorService實例
首先,我們需要一個可以在ZK代碼中使用的ExecutorService。 在大多數(shù)情況下,我們需要一個共享的單例實例,該實例可以通過依賴項注入(例如Spring)進(jìn)行配置和管理。 確保只創(chuàng)建一次ExecutorService,并且使用應(yīng)用程序?qū)⑵湔_關(guān)閉是非常重要的。
在這個示例項目中,我將使用一個簡單的holder類,該類管理單個靜態(tài)可用的ExecutorService實例的生命周期。 該持有人必須在zk.xml中配置為偵聽器 。
package sample;import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;import org.zkoss.zk.ui.WebApp; import org.zkoss.zk.ui.util.WebAppCleanup; import org.zkoss.zk.ui.util.WebAppInit;public class SampleExecutorHolder implements WebAppInit, WebAppCleanup {private static volatile ExecutorService executor;public static ExecutorService getExecutor() {return executor;}@Overridepublic void cleanup(WebApp wapp) throws Exception {if (executor != null) {executor.shutdown();System.out.println('ExecutorService shut down');}}@Overridepublic void init(WebApp wapp) throws Exception {executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());System.out.println('Initialized an ExecutorService');}}請注意,線程池是根據(jù)系統(tǒng)中的處理器使用固定大小配置的。 正確的線程池大小調(diào)整非常重要,并且取決于您打算執(zhí)行的任務(wù)類型。 最大線程數(shù)也是同時進(jìn)行的并發(fā)任務(wù)的最大數(shù)量!
2.編寫對后臺任務(wù)的結(jié)果進(jìn)行建模的事件類
我們將使用ZK服務(wù)器推送將任務(wù)結(jié)果傳達(dá)回UI,因此必須將結(jié)果建模為ZK事件。 創(chuàng)建自定義Event的子類而不是將結(jié)果添加到data參數(shù)中始終是一個好主意,因為自定義類更加類型安全并且可以支持多個字段。
第一個事件類表示任務(wù)仍在運(yùn)行時發(fā)送的狀態(tài)更新。 在此示例中,它將包含輸入字符串中的字符數(shù)。
package sample;import org.zkoss.zk.ui.event.Event;public class FirstStepEvent extends Event {public final int amountOfCharacters;public FirstStepEvent(int amountOfCharacters) {super('onFirstStepCompleted', null);this.amountOfCharacters = amountOfCharacters;}}第二個事件類表示完全完成的任務(wù)。 在此示例中,它包含大寫的輸入字符串。
package sample;import org.zkoss.zk.ui.event.Event;public class SecondStepEvent extends Event {public final String upperCaseResult;public SecondStepEvent(String upperCaseResult) {super('onSecondStepCompleted', null);this.upperCaseResult = upperCaseResult;}}
3.編寫任務(wù)類
任務(wù)類應(yīng)具有以下特征:
- 它實現(xiàn)了Runnable
- 它將所有必需的輸入數(shù)據(jù)作為構(gòu)造函數(shù)參數(shù)(如果可能,數(shù)據(jù)應(yīng)該是不可變的!)。 此輸入數(shù)據(jù)必須是線程安全的,并且通常不應(yīng)包含任何與ZK相關(guān)的內(nèi)容(無組件,會話等)。 例如,如果要使用文本框值作為輸入,請事先讀取該值,并且不要將文本框本身作為參數(shù)傳遞 。
- 它需要一個Desktop,以及至少一個EventListener作為構(gòu)造函數(shù)參數(shù)。 它們是將結(jié)果發(fā)送回UI所必需的
在此示例中,唯一的輸入數(shù)據(jù)是將用于計算任務(wù)結(jié)果的字符串。
package sample;import java.util.Locale;import org.zkoss.zk.ui.Desktop; import org.zkoss.zk.ui.DesktopUnavailableException; import org.zkoss.zk.ui.Executions; import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.EventListener;public class SampleTask implements Runnable {private final String input;private final Desktop desktop;private final EventListener<Event> eventListener;@SuppressWarnings({ 'rawtypes', 'unchecked' })public SampleTask(String input, Desktop desktop, EventListener eventListener) {this.input = input;this.desktop = desktop;this.eventListener = eventListener;}@Overridepublic void run() {try {// Step 1Thread.sleep(10000);Executions.schedule(desktop, eventListener, new FirstStepEvent(input.length()));// Step 2Thread.sleep(10000);Executions.schedule(desktop, eventListener, new SecondStepEvent(input.toUpperCase(Locale.ENGLISH)));} catch (DesktopUnavailableException e) {System.err.println('Desktop is no longer available: ' + desktop);} catch (InterruptedException e) {}}}注意所有構(gòu)造函數(shù)參數(shù)如何存儲在私有的final字段中,以及輸入數(shù)據(jù)如何不可變(Java中字符串是不可變的!)。 該任務(wù)通過使用Thread.sleep模擬長時間運(yùn)行的處理,并在“處理”完成一半時提交狀態(tài)事件。
4.在ZK作曲家中安排任務(wù)
在作曲家中使用任務(wù)非常簡單。 您只需要啟用服務(wù)器推送,并將新的任務(wù)實例提交給執(zhí)行者。 一旦有可用的后臺線程可用,它將自動啟動任務(wù)。
desktop.enableServerPush(true); // Get the executor from somewhere executor = SampleExecutorHolder.getExecutor(); executor.execute(new SampleTask(input.getValue(), desktop, this));在此示例中,編輯器擴(kuò)展了GenericForwardComposer,該實現(xiàn)實現(xiàn)了EventListener,因此它本身可以處理產(chǎn)生的任務(wù)事件。 這兩個事件均由使用狀態(tài)信息更新UI的方法處理。
public void onFirstStepCompleted(FirstStepEvent event) {status.setValue('Task running: ' + event.amountOfCharacters + ' characters in input'); }public void onSecondStepCompleted(SecondStepEvent event) {status.setValue('Task finished: ' + event.upperCaseResult); }
最后的話
使用此技術(shù)為ZK應(yīng)用程序中的長期運(yùn)行的任務(wù)添加強(qiáng)大的支持非常容易。 ZK編寫器中的結(jié)果代碼非常簡單,因為結(jié)果是使用典型的Event / EventListener范例傳遞的,該范例在ZK應(yīng)用程序中非常常見。
這種技術(shù)的最大危險是線程安全錯誤,這些錯誤很難調(diào)試。 完全了解執(zhí)行每段代碼的線程,并確保所有共享狀態(tài)都是完全線程安全的,這至關(guān)重要。 只要后臺任務(wù)本身不訪問其他非線程安全資源,使用不可變的輸入數(shù)據(jù)和不可變的輸出事件通常足以確保安全。 一些常見的錯誤是:
- 在后臺任務(wù)中調(diào)用線程本地相關(guān)的庫方法(例如,任何看起來神奇地獲得某種“當(dāng)前”值的方法)。 后臺線程不會自動包含與servlet線程相同的線程本地值,因此默認(rèn)情況下,所有這些方法都將失敗。 例如ZK中的Sessions.getCurrent(),Executions.getCurrent()和許多Spring Security靜態(tài)方法。
- 將非線程安全參數(shù)傳遞給后臺任務(wù)。 例如,傳遞一個可變的List,該可變的List可能在任務(wù)運(yùn)行時由編寫者修改(總是制作可變集合的副本!)。
- 在事件中傳遞非線程安全的結(jié)果數(shù)據(jù)。 例如,在結(jié)果事件中傳遞列表,而稍后將在任務(wù)中修改列表(始終復(fù)制可變集合!)。
- 在桌面中訪問非線程安全的方法。 即使您可以在后臺任務(wù)中訪問桌面,大多數(shù)桌面方法也不是線程安全的。 例如,不能保證調(diào)用desktop.isAlive()能夠正確返回狀態(tài)(至少在ZK 6.5中,該方法依賴于非易失性字段,因此不能保證在后臺線程中可見寫入)
參考: Advanced ZK:異步UI更新和后臺處理– Jawsy Solutions技術(shù)博客博客上的JCG合作伙伴 Joonas Javanainen的第二部分 。
翻譯自: https://www.javacodegeeks.com/2012/10/advanced-zk-asynchronous-ui-updates-and-background-processing-part-2.html
總結(jié)
以上是生活随笔為你收集整理的高级ZK:异步UI更新和后台处理–第2部分的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Win10杀毒软件哪家强?比特梵德、卡巴
- 下一篇: 苹果蜂窝数据怎么设置(苹果蜂窝数据怎么设