一、 前言
人們不得不等待。事實上,用戶在操作計算機時,如果等待時間超過大約200毫秒,他們一般都感到厭煩。當你的基於萬維網的應用程序使用一個需要耗費許多秒甚至幾分鐘的進程時,這可能成為一個問題。顯然,你不可能僅通過建立一個進度條對話框或一等待光標就算萬事大吉。
幸好,ASP.Net為此提供了一些不同的解決方案來處理這種相當耗時的進程-具體情況要依賴於要求的交互級別和你願意處理的復雜程度。本文首先通過一個示例應用程序來說明這個問題並且通過兩種方案來運行:一種使用了簡單查詢技術,而另一種使用一種更高級的AJax解決方案。
千萬警惕,在.Net框架中已發現存在一些錯誤的方式。其中之一就是IAsyncHTTPHandler-乍看來,它似乎有助於較長網頁的請求。然而,這個異步HTTP處理器卻是被設計用來釋放處理器的-盡管,此時在一頁面之上的某些任務需耗費一些時間但是並不需要任何CPU。一個好的例子就是在一頁面的中間發出Web請求。在這種情況中,異步HTTP處理器是很有效率的。
二、 問題
在本文中,我要討論一個不同的問題。在本示例應用程序中,我創建了一個頁面-它用於為五個不同的機場報告當前的溫度、風級和另外一些天氣信息。Web服務要花費大約五秒鐘來取得每一項數據。因此,如果我讓該頁面如圖1所示運行,那麼在服務器返回一頁面前要花費大約一分鐘-這對於任何用戶都是無法接受的等待時間。
圖1.等待:最開始的示例應用程序大約需要一分鐘來加載頁面。
這個相當耗時的頁面相應的Html顯示於列表1中(詳見下載源代碼)。
頁面裝載事件代碼為數據格子創建一個數據集。然後,迅速處理多個機場並且調用該web服務以得到數據。然後,該方法把數據從web服務填充到該數據集並且把它依附於一個格子控件(見圖2)。
圖2.基本的:該示例應用程序的Web服務執行一簡單的天氣狀況查詢。
這個web服務的WSDL是http://www.capeclear.com/AirportWeather.wsdl。它定義了許多不同的方法,我將僅使用其中的一個getSummary方法-它返回一個包括機場的位置、天空條件、風速、可見性甚至更多的數據塊。
用這種方式,即使單個服務器請求也要比單個頁面取回消耗更多的時間。另外一種選擇是讓一個線程運行於後台來取得數據,而由前端頁面連續地監視該線程的輸出。
三、 線程解決方案
線程解決方案提供給用戶一種更為干淨的體驗-因為它們可以周期性地得到處理的更新。這裡的響應是很容易准備的,盡管在後台的處理可能花一些時間,但是作為響應卻可以馬上返回。
為處理此線程系統,我將使用兩個類和一個接口。JobHandler singleton負責維持一個對象集合-它實現IJob接口。這個JobHandler管理系統線程。每添加一個工作創建一個新線程,並且該工作上的Start方法在一個新線程內被調用。一個被用於後面查詢工作的ID字符串被返回。
該Job系統相應的UML顯示於圖3。
圖3.這個屏幕快照顯示出該Job系統相應的UML。
WeatherJob是一個Ijob的實現-它負責從機場中的一個指定集合進行天氣查詢並且填充一個稱作Data的包含天氣報告的DataSet。
該JobHandler singleton相應的代碼顯示於列表2(
詳見下載源代碼)-相當直接。唯一有趣的一點是AddJob方法,它為該工作創建一個新的線程並調用Start方法。
這些工作的接口顯示於列表3(詳見下載源代碼)。其中的構造器為該工作建立數據集合。而且Start方法,通過每個機場,調用WEB請求並且在該數據集合中存儲返回的數據。
四、 查詢解決方案
監視天氣工作的第一種解決方案是使用查詢。為此,頁面將每隔兩秒向它自己回寄數據。請求工作在第一個頁面中就開始了。此後,該頁面將通過鉤住工作的數據輸送到頁面中的數據格子來監視天氣工作的輸出。浏覽器、WEB服務器以及線程之間的關系顯示於圖4。
圖4.查詢:查詢Html解決方案顯示了浏覽器、WEB服務器以及線程之間的關系。
針對該查詢頁面的Html顯示於列表4中(詳見下載源代碼)。其中,有趣的部分是在標簽refreshScript內部的腳本塊。當標簽是可見的時,將執行該腳本以在頁面加載兩秒後重新提交表單-這將更新在格子中的數據。
該查詢Html背後的代碼顯示於列表5(詳見下載源代碼)。這裡的重要代碼是位於page_load方法中。如果存儲在隱藏的表格字段中的請求ID是null或blank,則這是第一次加載頁面。在第一次加載頁面時,創建該工作並且該工作的ID被放置於隱藏的表單域中。
在兩秒以後,該javascript將被激發而該頁面將重加載。請求ID將第二次接近該隱藏的輸入字段並且該代碼將用指定的ID發現該工作並且使用該數據來填充數據格子。
五、 AJax解決方案
在Internet Explorer中的頁面重載會在頁面變成一片空白並等待重載之時造成一次聽得見的鼠標擊鍵和一次屏幕閃動。如果它每隔兩秒發生一次,這可能相當煩人。AJax提供了一種選擇-只有一個頁面加載並且頁面中的Javascript請求每隔500毫秒請求狀態時,才動態地更新頁面(見圖5)。
圖5.AJax解決方案:Javascript每500毫秒更新後台的頁面數據。
該AJax頁面的Html部分顯示於列表6(詳見下載源碼),其中的大多數代碼是Javascript。該Javascript首先激活addFIEld調用-它增加從服務器中以xml形式返回的不同字段。這個startup頁面開始第一次到服務器的請求。getData通過調用createHTTPRequest開始一個請求。這個函數通過具有跨平台的代碼來構建HTTP請求對象。
該HTTP請求是異步的。在請求完成時,調用handleResponse函數-該函數分析XML並且為該數據表格創建一些新的HTML-這個Html將被放置到"grid"<div>標簽中。
該頁面背後的代碼顯示於列表7。代碼中的page_load啟動該工作,然後用數據請求頁面的URL設置隱藏的輸入字段。
這個get_data.ASPx頁面使用一個請求ID並且返回一個當前數據集合的XML描述。該頁面代碼顯示於下:
//get_data.ASPx
<%@ Page language="c#" Codebehind="get_data.ASPx.cs"
AutoEventWireup="false" Inherits="background.get_data" %>
顯然,在這種情況下後台的代碼更為重要。該代碼顯示於列表8中(詳見下載源碼),-它首先把響應的內容類型設置為"text/xml"。如果在浏覽器中不存在該AJax代碼,那麼就不會從響應中生成一個XML文檔。此後,代碼得到請求並且要求DataSet生成該XML。然後它稍微改變一下該XML響應來添加"done"字段-這個用於告訴客戶請求是否已完成。
在該頁面第一次啟動時,它看上去如圖6的樣子。
圖6.仍是查詢:這個屏幕快照顯示出仍處於查詢中的AJax頁面。
當請求完成時,該浏覽器將看起來如圖7所示。在用AJAX解決方案時,請記住,在創建代碼時,你是在設置最小的浏覽器要求-並不是所有的浏覽器都能創建一HTTP請求。事實上,只是最近的浏覽器才能實現它。理想情況下,你的解決方案應該既為更舊的浏覽器提供查詢版本支持也為新型浏覽器提供一個AJax版本支持。
圖7.完成:這個屏幕快照顯示出完成後的AJax頁面。
六、 小結 在最有利的情況下,線程也可能存在問題。而在這種情況下,線程可能比平常更難於監控-因為它運行於服務器的後台。當然,即使沒有Web客戶在監控它,請求仍有可能將繼續保持運行。如果這會是一個問題,那麼你應該讓WEB監控代碼用線程化的過程設置一個時間戳。如果該線程化過程發現一段時間後自己還沒有被觀看,那麼它就可以取消自己。
在.NET中有幾種方法可以實現後台處理,而本文的線程方法僅是其中的一種。你也可以用ASP.Net緩存工作,或甚至創建一個真正的後台處理進程。
通過使用這些不同的技術,你就完全可以把相當耗時的處理等待轉變成一種有關過程處理的豐富的回饋式用戶體驗。