可以通过 AsyncController 类编写异步操作方法。 可以对长时间运行的、非 CPU 绑定的请求使用异步操作方法。 这样可避免在处理请求时阻塞 Web 服务器执行工作。 AsyncController 类通常用于长时间运行的 Web 服务调用。
本主题包含以下各节:
与本主题对应的包含源代码的 Visual Studio 项目可从 Download(下载)网页获得。
在 Web 服务器上,.NET Framework 维护一个用于服务 ASP.NET 请求的线程池。 当请求到达时,将调度池中的线程以处理该请求。 如果对请求进行同步处理,则在处理请求时将阻塞处理请求的线程,并且该线程不能对另一个请求提供服务。
这可能不是一个问题,因为线程池可以设置得足够大以容纳许多阻塞的线程。 但是,线程池中的线程数目是有限制的。 在同时处理多个长时间运行的请求的大型应用程序中,可能会阻塞所有可用的线程。 这种情况称为“线程不足”。 当出现这种情况时,Web 服务器会将请求排队。 如果请求队列已满,则 Web 服务器会拒绝请求并处于 HTTP 503 状态(服务器太忙)。
在可能出现线程不足的应用程序中,您可以配置通过异步方式处理操作。 异步请求与同步请求所需的处理时间相同。 例如,如果某个请求生成一个需要两秒钟来完成的网络调用,则该请求无论是同步执行还是异步执行都需要两秒钟。 但是,在异步调用的过程中,服务器在等待第一个请求完成的过程中不会阻塞对其他请求的响应。 因此,当有许多请求调用长时间运行的操作时,异步请求可以防止出现请求排队的情况。
在调用异步操作时,将执行以下步骤:
下图显示了异步模式。
本节列出了有关何时使用同步操作方法或异步操作方法的准则。 这只是一些准则;您必须逐个检查每个应用程序以确定异步操作方法是否能帮助提高性能。
通常,在满足以下条件时使用同步管线:
通常,在满足以下条件时使用异步管线:
下载的示例演示如何有效地使用异步操作方法。 示例程序调用 Sleep 方法来模拟长时间运行的进程。 很少有产品应用程序会显示出如此明显的使用异步操作方法的好处。
您应测试应用程序以确定异步方法是否能提供性能好处。 在某些情况下,增加每个 CPU 的 IIS 最大并发请求数和每个 CPU 的最大并发线程数可能会更好。 有关 ASP.NET 线程配置的更多信息,请参见 Thomas Marquardt 的博客上的文章 ASP.NET Thread Usage on IIS 7.0 and 6.0(ASP.NET 线程在 IIS 7.0 和 6.0 上的使用情况)。 有关何时执行异步数据库调用的更多信息,请参见 Rick Anderson 博客上的文章 Should my database calls be Asynchronous?(我的数据库调用是否应采用异步方式?)。
很少有应用程序要求所有的操作方法都是异步的。 通常,将少量的同步操作方法转换为异步方法就会显著增加所需的工作量。
下面的代码示例演示了一个同步操作方法,它用于显示来自门户网站控制器的新闻项。 请求 Portal/News?city=Seattle 显示 Seattle 的新闻。
C#
VB
public class PortalController: Controller { public ActionResult News(string city) {
NewsService newsService = new NewsService();
ViewStringModel headlines = newsService.GetHeadlines(city); return View(headlines);
}
}
下面的示例演示了重新编写为异步方法的 News 操作方法。
C#
VB
public class PortalController : AsyncController { public void NewsAsync(string city) {
AsyncManager.OutstandingOperations.Increment();
NewsService newsService = new NewsService();
newsService.GetHeadlinesCompleted += (sender, e) =>
{
AsyncManager.Parameters["headlines"] = e.Value;
AsyncManager.OutstandingOperations.Decrement();
};
newsService.GetHeadlinesAsync(city);
} public ActionResult NewsCompleted(string[] headlines) { return View("News", new ViewStringModel
{
NewsHeadlines = headlines
});
}
}
将同步操作方法转换为异步操作方法包含以下步骤:
由 NewsAsync 方法使用的 NewsService 类是一个使用基于事件的异步模式公开方法的服务示例。 有关此模式的更多信息,请参见基于事件的异步模式概述。
OutstandingOperations 属性通知 ASP.NET 有多少个操作已挂起。 这是必要的,因为 ASP.NET 不能确定由操作方法启动了多少个操作或这些操作何时完成。 当 OutstandingOperations 属性为零时,ASP.NET 可通过调用 NewsCompleted 方法来完成整个异步操作。
请注意下面有关异步操作方法的一些事项:
当操作必须执行几个独立的操作时,异步操作方法很有用。 例如,门户网站可能不只显示新闻,还显示体育、天气、股票和其他信息。
下面的示例演示了新闻门户网站 Index 操作方法的同步版本。
C#
VB
public ActionResult IndexSynchronous( string city ) {
NewsService newsService = new NewsService(); string[] headlines = newsService.GetHeadlines();
SportsService sportsService = new SportsService(); string[] scores = sportsService.GetScores();
WeatherService weatherService = new WeatherService(); string[] forecast = weatherService.GetForecast(); return View("Common", new PortalViewModel {
NewsHeadlines = headlines,
SportsScores = scores,
Weather = forecast
});
}
按顺序执行对每个服务的调用。 因此,为了响应请求所需的时间是每个服务调用的时间加上少量系统开销的时间的总和。 例如,如果各个调用分别用了 400、500 和 600 毫秒,则总的响应时间将稍微大于 1.5 秒。 但是,如果异步执行服务调用(以并行方式),则总的响应时间将稍微大于 600 毫秒,因为这是最长任务的持续时间。
下面的示例演示了新闻门户网站 Index 操作方法的异步版本。
C#
VB
public void IndexAsync(string city) {
AsyncManager.OutstandingOperations.Increment(3);
NewsService newsService = new NewsService();
newsService.GetHeadlinesCompleted += (sender, e) =>
{
AsyncManager.Parameters["headlines"] = e.Value;
AsyncManager.OutstandingOperations.Decrement();
};
newsService.GetHeadlinesAsync();
SportsService sportsService = new SportsService();
sportsService.GetScoresCompleted += (sender, e) =>
{
AsyncManager.Parameters["scores"] = e.Value;
AsyncManager.OutstandingOperations.Decrement();
};
sportsService.GetScoresAsync();
WeatherService weatherService = new WeatherService();
weatherService.GetForecastCompleted += (sender, e) =>
{
AsyncManager.Parameters["forecast"] = e.Value;
AsyncManager.OutstandingOperations.Decrement();
};
weatherService.GetForecastAsync();
}public ActionResult IndexCompleted(string[] headlines, string[] scores, string[] forecast) { return View("Common", new PortalViewModel {
NewsHeadlines = headlines,
SportsScores = scores,
Weather = forecast
});
}
}
在前面的示例中,使用参数 3 调用 Increment 方法,这是因为有三个异步操作。
如果要将特性应用于异步操作方法,则将它们应用于 ActionAsync 方法,而不是应用于 ActionCompleted 方法。 忽略 ActionCompleted 方法上的特性。
已添加两个新的特性:AsyncTimeoutAttribute 和 NoAsyncTimeoutAttribute。 这些特性可让您控制异步超时时间。
如果异步操作方法调用一个使用 BeginMethod/EndMethod 模式公开方法的服务,则回调方法(即作为异步回调参数传递到 Begin 方法的方法)可能会在一个不由 ASP.NET 控制的线程上执行。 在此情况下,HttpContext.Current 将为 null,并且当应用程序访问 AsyncManager 类的成员(例如 Parameters)时可能会出现争用条件。 若要确保已访问 HttpContext.Current 实例并避免争用条件,则可以通过从回调方法中调用 Sync() 来还原 HttpContext.Current。
如果回调同步完成,则回调将在由 ASP.NET 控制的线程上执行并且将对操作进行序列化,因此不会出现并发问题。 从已经由 ASP.NET 控制的线程中调用 Sync() 具有未定义的行为。
将总是在由 ASP.NET 控制的线程上调用 ActionCompleted 方法。 因此,不要从该方法中调用 Sync()。
传递到 Begin 方法的回调可能会使用由 ASP.NET 控制的线程来进行调用。 因此,您必须在调用 Sync() 之前检查此条件。 如果操作已同步完成(即,如果 CompletedSynchronously 为 true),则回调在原始线程上执行,并且您不必调用 Sync()。 如果操作已异步完成,(即,CompletedSynchronously 为 false),则回调在线程池或 I/O 完成端口线程上执行,并且您必须 Sync()。
有关 BeginMethod/EndMethod 模式的更多信息,请参见异步编程概述和 Rick Anderson 的博客上的文章 Using the BeginMethod/EndMethod pattern with MVC(对 MVC 使用 BeginMethod/EndMethod 模式)。
下表列出了异步操作方法的关键类。
类 | 说明 |
---|---|
AsyncController | 为异步控制器提供基类。 |
AsyncManager | 为 AsyncController 类提供异步操作。 |
ASP.NET MVC 应用程序中的控制器和操作方法