在第 1 部分和第 2 部分中,建立了 WeatherDataSource 控件,該控件針對 weather.com(英文)所提供的 XML API 來運行,使用 WebRequest 和 WebResponse 來通過 HTTP 訪問數據。迄今為止,均是同步訪問該服務。因此, 頁面處理被阻止,直到 Web 請求完成為止。此方法對於測試頁面是有效的,在 小站點上也可能有效,但是在接收大量通信流量的站點上則會慘敗;例如門戶頁 面,天氣模塊在其中可能非常常見。
引言
在線程池中有固定不變的大量線程可用於服務請求,遺憾的是,該解決方案 並非僅僅提高限制(還會增加線程占用資源以及 CPU 占用資源)。因此,當一 個頁面被阻止而等候另一個服務器時,它還在占用線程,因而可能會導致其他傳 入的請求在隊列中等候更長的時間。這將導致對站點的訪問變慢,並降低 CPU 的利用率。在 Visual Studio 2005 中,我們引入了異步頁面,這使得控件能夠 定義它們希望異步完成的任務,即,無需阻止用來處理請求的線程。在此將不介 紹異步頁面本身的詳細信息,Dmitry(英文)和 Fritz Onion(英文)中以前已 經有所介紹。此處要介紹的是如何在數據源控件中利用此功能,使用加載項框架 來實現異步數據源。
背景
在第 1 部分中,間接提到了 DataSourceView 類的有些古怪的設計:
public abstract class DataSourceView {
public virtual void Select(DataSourceSelectArguments arguments,
DataSourceViewSelectCallback callback);
protected abstract IEnumerable ExecuteSelect(
DataSourceSelectArguments arguments);
...
}
您會注意到,公共 Select 方法實際上並不返回任何數據。而是接受一個回 撥,並通過該回撥來返回數據。它只調用受保護的 ExecuteSelect(它始終執行 同步數據訪問)來檢索要退還給數據綁定控件的數據。DataSourceView 類的默 認實現實際上不會異步執行任何操作。原因在於,並不存在任何現成的異步數據 源控件。但 OM 的設計確實允許實現異步數據訪問,在這種設計下,數據在異步 工作完成之後才可用。因此,我們就有了一個基於回撥的模型。
那些熟悉框架中的異步 API 的人會注意到缺少了異步模式:公共 Select、 BeginSelect 和 EndSelect 方法,在這些方法中,數據綁定控件選擇要調用哪 些方法。但是,數據綁定控件並不能確定是選擇同步 API 還是選擇異步 API。 此外,在數據綁定控件上添加屬性也毫無作用。數據源控件封裝了有關如何訪問 數據存儲的詳細信息,對數據存儲的訪問是同步發生還是異步發生應該根據數據 源是否基於語義來決定或者根據自定義屬性來決定。潛在的“bool PerformAsyncDataAccess”屬性的正確位置適合於數據源控件本身。這還使得數 據源控件可以使用一種方法來執行數據訪問,即使多個數據綁定控件被綁定到同 一個數據源。至此已多次解釋了該體系結構所蘊涵的這些微妙的概念,但願能闡 明該設計。
關於異步任務,最後要注意的一點是:頁面是否應該執行任何異步工作完全 由頁面開發人員最終決定(通過 Page 指令的 Async 屬性)。因此,任何編寫 良好的數據源控件必須退化為根據需要來執行同步數據訪問。
框架
在此框架中(在此系列結尾會用示例的剩余部分來演示這一點),已將 AsyncDataSource 和 AsyncDataSourceView 基類放在一起,這些基類可以用於 實現能夠執行異步數據訪問的數據源控件。以下大概介紹了框架內容,以及有助 於弄清楚其含義的一些注釋:
public abstract class AsyncDataSourceControl : DataSourceControl,
IAsyncDataSource {
private bool _performAsyncDataAccess;
protected AsyncDataSourceControl() {
_performAsyncDataAccess = true;
}
public virtual bool PerformAsyncDataAccess {
get; set;
}
bool IAsyncDataSource.IsAsync {
get { return _performAsyncDataAccess && Page.IsAsync; }
}
}
public abstract class AsyncDataSourceView : DataSourceView {
protected abstract IAsyncResult BeginExecuteSelect(
DataSourceSelectArguments arguments,
AsyncCallback asyncCallback,
object asyncState);
protected abstract IEnumerable EndExecuteSelect(
IAsyncResult asyncResult);
protected override IEnumerable ExecuteSelect(
DataSourceSelectArguments arguments) {
//實現從 DataSourceView 中繼承的
//抽象 ExecuteSelect 方法,
//方法是使用 BeginExecuteSelect 和 EndExecuteSelect,
//以便通過阻止來
//進行同步數據訪問。
}
private IAsyncResult OnBeginSelect(object sender,
EventArgs e, AsyncCallback asyncCallback,
object extraData);
private void OnEndSelect(IAsyncResult asyncResult);
public override void Select(DataSourceSelectArguments arguments,
DataSourceViewSelectCallback callback) {
if (_owner.IsAsync) {
//使用 OnBeginSelect 和 OnEndSelect
//作為 BeginEventHandler 和 EndEventHandler 方法,
//來調用 Page.RegisterAsyncTask,
//以指明需要
//進行異步工作。這些方法將依次
//調用特定的
//數據源實現,方法是調用
//已在此類中引入的
//抽象 BeginExecuteSelect 和 EndExecuteSelect
//方法。
}
else {
//執行同步數據訪問
base.Select(arguments, callback);
}
}
...
}