在第 部分和第 部分中建立了 WeatherDataSource 控件该控件针对 (英文)所提供的 XML API 来运行使用 WebRequest 和 WebResponse 来通过 HTTP 访问数据迄今为止均是同步访问该服务因此页面处理被阻止直到 Web 请求完成为止此方法对于测试页面是有效的在小站点上也可能有效但是在接收大量通信流量的站点上则会惨败例如门户页面天气模块在其中可能非常常见
引言
在线程池中有固定不变的大量线程可用于服务请求遗憾的是该解决方案并非仅仅提高限制(还会增加线程占用资源以及 CPU 占用资源)因此当一个页面被阻止而等候另一个服务器时它还在占用线程因而可能会导致其他传入的请求在队列中等候更长的时间这将导致对站点的访问变慢并降低 CPU 的利用率在 Visual Studio 中我们引入了异步页面这使得控件能够定义它们希望异步完成的任务即无需阻止用来处理请求的线程在此将不介绍异步页面本身的详细信息Dmitry(英文)和 Fritz Onion(英文)中以前已经有所介绍此处要介绍的是如何在数据源控件中利用此功能使用加载项框架来实现异步数据源
背景
在第 部分中间接提到了 DataSourceView 类的有些古怪的设计
public abstract class DataSourceView {
public virtual void Select(DataSourceSelectArguments arguments
DataSourceViewSelectCallback callback);
protected abstract IEnumerable ExecuteSelect(
DataSourceSelectArguments arguments);
}
您会注意到公共 Select 方法实际上并不返回任何数据而是接受一个回拨并通过该回拨来返回数据它只调用受保护的 ExecuteSelect(它始终执行同步数据访问)来检索要退还给数据绑定控件的数据DataSourceView 类的默认实现实际上不会异步执行任何操作原因在于并不存在任何现成的异步数据源控件但 OM 的设计确实允许实现异步数据访问在这种设计下数据在异步工作完成之后才可用因此我们就有了一个基于回拨的模型
那些熟悉框架中的异步 API 的人会注意到缺少了异步模式公共 SelectBeginSelect 和 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 IAsyncDataSourceIsAsync {
get { return _performAsyncDataAccess && PageIsAsync; }
}
}
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 (_ownerIsAsync) {
//使用 OnBeginSelect 和 OnEndSelect
//作为 BeginEventHandler 和 EndEventHandler 方法
//来调用 PageReGISterAsyncTask
//以指明需要
//进行异步工作这些方法将依次
//调用特定的
//数据源实现方法是调用
//已在此类中引入的
//抽象 BeginExecuteSelect 和 EndExecuteSelect
//方法
}
else {
//执行同步数据访问
baseSelect(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) {
argumentsRaiseUnsupportedCapabilitiesError(this);
string zipCode = _ownerGetSelectedZipCode();
if (zipCodeLength == ) {
return new SynchronousAsyncSelectResult(/* selectResult */
null
asyncCallback asyncState);
}
_weatherService = new WeatherService(zipCode);
return _weatherServiceBeginGetWeather(asyncCallback asyncState);
}
protected override IEnumerable EndExecuteSelect(IAsyncResult asyncResult) {
SynchronousAsyncSelectResult syncResult =
asyncResult as SynchronousAsyncSelectResult;
if (syncResult != null) {
return syncResultSelectResult;
}
else {
Weather weatherObject =
_weatherServiceEndGetWeather(asyncResult);
_weatherService = null;
if (weatherObject != null) {
return new Weather[] { weatherObject };
}
}
return null;
}
}
要注意的关键问题是在使用该框架时只需要实现 BeginExecuteSelect 和 EndExecuteSelect在它们的实现过程中通常要调用由该框架中的各种对象(例如 WebRequest 或 IO 流)所揭示的 BeginXXX 和 EndXXX 方法(在 Visual Studio 中还需要调用 SqlDataCommand)并返回 IAsyncResult在此示例中有一个封装了基础 WebRequest 对象的 WeatherService 帮助程序类
对于那些实际缺少异步模式的框架您在此会看到有效的异步模式以及您要实现的 BeginExecuteSelect 和 EndExecuteSelect和您要调用以返回 IAsyncResult 实例的 Begin 和 End 方法
最有趣的可能是 SynchronousAsyncSelectResult 类(在某种程度上而言是一种矛盾)此类是框架附带的它基本上是一个 IAsyncResult 实现可使数据立即可用并从其 IAsyncResultCompletedSynchronously 属性报告 true到目前为止这仅适用于未选择邮政编码的情况并且需要返回 null(启动异步任务而只返回 null 是没有意义的)但正如您会在下文中看到的这在其他方案中也是有用的
页面基础结构隐藏了在 Microsoft 上下文中执行异步工作的大部分详细信息希望在此提供的框架使您执行最少的操作就能编写使用此基础结构的数据源不过就其本质而言实现异步行为是复杂的有时候第一次阅读本文时会有一些疑问而第二次阅读时可能就明白了您可以使用下面我的评论表单来发送问题或进行讨论