首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >在 ASP.NET MVC 中使用异步控制器

在 ASP.NET MVC 中使用异步控制器

作者头像
逸鹏
发布2018-04-10 09:58:19
1.8K0
发布2018-04-10 09:58:19
举报
文章被收录于专栏:逸鹏说道逸鹏说道

可以通过 AsyncController 类编写异步操作方法。 可以对长时间运行的、非 CPU 绑定的请求使用异步操作方法。 这样可避免在处理请求时阻塞 Web 服务器执行工作。 AsyncController 类通常用于长时间运行的 Web 服务调用。

本主题包含以下各节:

  • 线程池处理请求的方式
  • 处理异步请求
  • 选择同步操作方法或异步操作方法
  • 将同步操作方法转换为异步操作方法
  • 并行执行多个操作
  • 将特性添加到异步操作方法
  • 使用 BeginMethod/EndMethod 模式
  • 类参考

与本主题对应的包含源代码的 Visual Studio 项目可从 Download(下载)网页获得。

线程池处理请求的方式

在 Web 服务器上,.NET Framework 维护一个用于服务 ASP.NET 请求的线程池。 当请求到达时,将调度池中的线程以处理该请求。 如果对请求进行同步处理,则在处理请求时将阻塞处理请求的线程,并且该线程不能对另一个请求提供服务。

这可能不是一个问题,因为线程池可以设置得足够大以容纳许多阻塞的线程。 但是,线程池中的线程数目是有限制的。 在同时处理多个长时间运行的请求的大型应用程序中,可能会阻塞所有可用的线程。 这种情况称为“线程不足”。 当出现这种情况时,Web 服务器会将请求排队。 如果请求队列已满,则 Web 服务器会拒绝请求并处于 HTTP 503 状态(服务器太忙)。

处理异步请求

在可能出现线程不足的应用程序中,您可以配置通过异步方式处理操作。 异步请求与同步请求所需的处理时间相同。 例如,如果某个请求生成一个需要两秒钟来完成的网络调用,则该请求无论是同步执行还是异步执行都需要两秒钟。 但是,在异步调用的过程中,服务器在等待第一个请求完成的过程中不会阻塞对其他请求的响应。 因此,当有许多请求调用长时间运行的操作时,异步请求可以防止出现请求排队的情况。

在调用异步操作时,将执行以下步骤:

  1. Web 服务器从线程池(辅助线程)获取一个线程并安排它处理传入请求。 此辅助线程启动一个异步操作。
  2. 将此辅助线程返回到线程池以对另一个 Web 请求提供服务。
  3. 在异步操作完成时通知 ASP.NET。
  4. Web 服务器从线程池获取一个线程(可能是与启动异步操作的线程不同的线程)以处理请求的其余部分,包括呈现响应。

下图显示了异步模式。

选择同步操作方法或异步操作方法

本节列出了有关何时使用同步操作方法或异步操作方法的准则。 这只是一些准则;您必须逐个检查每个应用程序以确定异步操作方法是否能帮助提高性能。

通常,在满足以下条件时使用同步管线:

  • 操作很简单或运行时间很短。
  • 简单性比效率更重要。
  • 此操作主要是 CPU 操作而不是包含大量的磁盘或网络开销的操作。 对 CPU 绑定操作使用异步操作方法未提供任何好处并且还导致更多的开销。

通常,在满足以下条件时使用异步管线:

  • 操作是网络绑定的或 I/O 绑定的而不是 CPU 绑定的。
  • 测试显示阻塞操作对于网站性能是一个瓶颈,并且通过对这些阻塞调用使用异步操作方法,IIS 可对更多的请求提供服务。
  • 并行性比代码的简单性更重要。
  • 您希望提供一种可让用户取消长时间运行的请求的机制。

下载的示例演示如何有效地使用异步操作方法。 示例程序调用 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
        });
    }
}

将同步操作方法转换为异步操作方法包含以下步骤:

  1. 不要从 Controller 派生控制器,而应从 AsyncController 派生。 从 AsyncController 派生的控制器使 ASP.NET 能够处理异步请求,并且这些控制器仍然可以为同步操作方法提供服务。
  2. 为操作创建两个方法。 启动异步进程的方法必须具有一个由操作和后缀“Async”组成的名称。 异步进程完成(回调方法)时调用的方法必须具有一个由操作和后缀“Completed”组成的名称。 在前面的示例中,News 方法已转换为两个方法:NewsAsync 和 NewsCompleted。 NewsAsync 方法返回 void(在 Visual Basic 中没有任何值)。 NewsCompleted 方法返回 ActionResult 实例。 尽管操作由两个方法组成,但使用与同步操作方法相同的 URL 来访问它(例如 Portal/News?city=Seattle)。 其他方法(例如 RedirectToAction 和 RenderAction)还是将按照 News 而不是 NewsAsync 来引用操作方法。 传递到 NewsAsync 的参数使用普通的参数绑定机制。 传递到 NewsCompleted 的参数使用 Parameters 字典。
  3. 使用异步操作方法中的异步调用替换原始 ActionResult 方法中的同步调用。 在上面的示例中,使用对 newsService.GetHeadlinesAsync 的调用替换对 newsService.GetHeadlines 的调用。

由 NewsAsync 方法使用的 NewsService 类是一个使用基于事件的异步模式公开方法的服务示例。 有关此模式的更多信息,请参见基于事件的异步模式概述。

OutstandingOperations 属性通知 ASP.NET 有多少个操作已挂起。 这是必要的,因为 ASP.NET 不能确定由操作方法启动了多少个操作或这些操作何时完成。 当 OutstandingOperations 属性为零时,ASP.NET 可通过调用 NewsCompleted 方法来完成整个异步操作。

请注意下面有关异步操作方法的一些事项:

  • 如果操作名称为 Sample,则框架将查找 SampleAsync 和 SampleCompleted 方法。
  • 视图页应命名为 Sample.aspx,而不是命名为 SampleAsync.aspx 或 SampleCompleted.aspx。 (操作名称为 Sample,而不是为SampleAsync。)
  • 控制器不能包含名为 SampleAsync 的异步方法和名为 Sample 的同步方法。 如果包含这两个方法,则会引发 AmbiguousMatchException异常,因为 SampleAsync 操作方法和 Sample 操作方法具有相同的请求签名。

并行执行多个操作

当操作必须执行几个独立的操作时,异步操作方法很有用。 例如,门户网站可能不只显示新闻,还显示体育、天气、股票和其他信息。

下面的示例演示了新闻门户网站 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 模式

如果异步操作方法调用一个使用 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 应用程序中的控制器和操作方法

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2016-02-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 我为Net狂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 线程池处理请求的方式
  • 处理异步请求
  • 选择同步操作方法或异步操作方法
  • 将同步操作方法转换为异步操作方法
  • 并行执行多个操作
  • 将特性添加到异步操作方法
  • 使用 BeginMethod/EndMethod 模式
  • 类参考
  • 请参见
    • 概念
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档