在第 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 上下文中執行非同步工作的大部分詳細資料。希望在此提供的架構使您執行最少的操作就能編寫使用此基礎結構的資料來源。不過,就其本質而言,實現非同步行為是複雜的。有時候,第一次閱讀本文時會有一些疑問,而第二次閱讀時可能就明白了。您可以使用下面“我的評論”表單來發送問題或進行討論。