在
第 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);
}
}
...
}
示例 現在,新的 AsyncWeatherDataSource 將從 AsyncDataSourceControl 中派生,而 AsyncWeatherDataSourceView 將從 AsyncDataSourceView 中派生。
public class AsyncWeatherDataSource : AsyncDataSourceControl {
//與 WeatherDataSource 相同
}
private sealed class AsyncWeatherDataSourceView : AsyncDataSourceView {
private AsyncWeatherDataSource _owner;
private WeatherService _weatherService;
public AsyncWeatherDataSourceView(AsyncWeatherDataSource owner,
string viewName)
: base(owner, viewName) {
_owner = owner;
}
protected override IAsyncResult BeginExecuteSelect(DataSourceSelectArguments arguments,
AsyncCallback asyncCallback,
object asyncState) {
arguments.RaiseUnsupportedCapabilitiesError(this);
string zipCode = _owner.GetSelectedZipCode();
if (zipCode.Length == 0) {
return new SynchronousAsyncSelectResult(/* selectResult */
null,
asyncCallback, asyncState);
}
_weatherService = new WeatherService(zipCode);
return _weatherService.BeginGetWeather(asyncCallback, asyncState);
}
protected override IEnumerable EndExecuteSelect(IAsyncResult asyncResult) {
SynchronousAsyncSelectResult syncResult =
asyncResult as SynchronousAsyncSelectResult;
if (syncResult != null) {
return syncResult.SelectResult;
}
else {
Weather weatherObject =
_weatherService.EndGetWeather(asyncResult);
_weatherService = null;
if (weatherObject != null) {
return new Weather[] { weatherObject };
}
}
return null;
}
}
要注意的關鍵問題是,在使用該框架時,只需要實現 BeginExecuteSelect 和 EndExecuteSelect。在它們的實現過程中,通常要調用由該框架中的各種對象(例如 WebRequest 或 IO 流)所揭示的 BeginXXX 和 EndXXX 方法(在 Visual Studio 2005 中,還需要調用 SqlDataCommand),並返回 IAsyncResult。在此示例中,有一個封裝了基礎 WebRequest 對象的 WeatherService 幫助程序類。
對於那些實際缺少異步模式的框架,您在此會看到有效的異步模式;以及您要實現的 BeginExecuteSelect 和 EndExecuteSelect,和您要調用以返回 IAsyncResult 實例的 Begin 和 End 方法。
最有趣的可能是 SynchronousAsyncSelectResult 類(在某種程度上而言是一種矛盾)。此類是框架附帶的。它基本上是一個 IAsyncResult 實現,可使數據立即可用,並從其 IAsyncResult.CompletedSynchronously 屬性報告 true。到目前為止,這僅適用於未選擇郵政編碼的情況,並且需要返回 null(啟動異步任務而只返回 null 是沒有意義的),但正如您會在下文中看到的,這在其他方案中也是有用的。
頁面基礎結構隱藏了在 Microsoft ASP.NET 上下文中執行異步工作的大部分詳細信息。希望在此提供的框架使您執行最少的操作就能編寫使用此基礎結構的數據源。不過,就其本質而言,實現異步行為是復雜的。有時候,第一次閱讀本文時會有一些疑問,而第二次閱讀時可能就明白了。您可以使用下面“我的評論”表單來發送問題或進行討論。