在asp.net-mvc中,如何在不降低用户体验的情况下运行昂贵的操作

本文关键字:情况下 体验 运行 操作 用户 net-mvc asp | 更新日期: 2023-09-27 17:58:40

我有一个asp.net-mvc网站,我的ORM使用nhibernate。

我有一个当前的控制器操作,它执行基本的CRUD更新(从数据库中查询一个项,然后更新一组值并提交回数据库表)。然后,它向客户端返回一个简单的json响应,以指示成功或错误。

 public ActionResult UpdateEntity(MyEntity newEntity)
 {
      var existingEntity = GetFromRepository(newEntity.Id);
      UpdateExistingEntity(newEntity, existingEntity);
      return Json(SuccessMessage);
 }

在某些情况下(假设提交成功,并且对象中的某些字段发生了更改),我现在想触发一些额外的操作(比如给一群人发电子邮件和运行一些生成报告的代码),但我不想降低更新人员的用户体验。所以我担心的是,如果我这样做:

 public ActionResult UpdateEntity(MyEntity newEntity)
 {
      var existingEntity = GetFromRepository(newEntity.Id);
      bool keyFieldsHaveChanged = UpdateExistingEntity(newEntity, existingEntity);
      if (keyFieldsHaveChanged)
     {
          GenerateEmails();
          GenerateReports();
     }
    return Json(SuccessMessage);
 }

这对于某人更新的用户体验来说太慢了。有没有(asyngc?)让一个昂贵的操作从控制器动作中触发,但控制器动作不会因此而减慢?

在asp.net-mvc中,如何在不降低用户体验的情况下运行昂贵的操作

我以前做过。

最健壮的方法是使用异步控制器,或者更好的是使用独立服务,如WCF服务。

但根据我的经验,我只需要做"简单"的、单一的任务,比如审计或报告,正如你所说。

在这个例子中,简单的方法是发射Task:

public ActionResult Do()
{
    SomethingImportantThatNeedsToBeSynchronous();
    Task.Factory.StartNew(() => 
    {
       AuditThatTheUserShouldntCareOrWaitFor();
       SomeOtherOperationTheUserDoesntCareAbout();
    });
    return View();
}

这是一个简单的例子。你可以随心所欲地启动任务,同步它们,在它们完成时得到通知,等等。

我目前已经使用上面的内容来上传AmazonS3。

如果打算立即返回JSON,并在不影响用户体验的情况下让密集的工作在后台异步运行,那么您需要启动一个新的后台线程。AsyncController在这里对您没有帮助。

有很多关于通过这样做来饿死线程请求池的争论(这是真的),但相反的争论是,因为服务器正忙于工作,所以应该饿死池。当然,理想情况下,您应该完全通过排队/分布式系统等将工作转移到另一台服务器上,但这是一个复杂的解决方案。除非你需要处理数百个请求,否则你不需要考虑这个选项,因为它不太可能引起问题。这实际上取决于解决方案的可扩展性要求、后台进程需要多长时间以及调用频率。最简单的解决方案如下:

public ActionResult UpdateEntity(MyEntity newEntity)
{
    var existingEntity = GetFromRepository(newEntity.Id);
    bool keyFieldsHaveChanged = UpdateExistingEntity(newEntity, existingEntity);
    if (keyFieldsHaveChanged)
    {
        ThreadPool.QueueUserWorkItem(o =>
                                        {
                                            GenerateEmails();
                                            GenerateReports();
                                        });
    }
    return Json(SuccessMessage);
}

您应该执行异步操作和异步控制器,以免锁定线程池,也不会让网站的其他用户受苦。当任务长时间运行时,从asp.net线程池中获取的线程将被保留,直到操作完成后才会返回到池中。如果有许多同时长时间运行的任务,那么它们将保留许多线程,所以访问您网站的其他用户很有可能会因为等待而受苦。异步操作不会使任何代码更快。我建议您只对线程使用异步控制器,但这还不够。我认为您应该使用一些链接或ajax来触发服务器上的操作,让用户继续在网站上冲浪。操作完成后,在下一次刷新页面时,应通知用户任务已完成执行。这里还有另一个证明,业务代码不应该写在控制器中。你应该为此分开服务。

这是一个老问题,所以我认为它需要更新。

我建议使用HangFire(http://hangfire.io)。这样,即使在web应用程序中,您也可以简单地将您的工作排入队列。HangFire将确保作业至少运行一次。

// Static methods are for demo purposes
BackgroundJob.Enqueue(
    () => Console.WriteLine("Simple!"));

您还可以在漂亮的UI中查看所有排队作业的状态。

我认为你真的想要一个类似于Windows Azure的Worker角色。

http://www.microsoft.com/windowsazure/features/compute/

我不知道如何在没有Azure排队的情况下在纯MVC中最好地实现这一点。而且,根据你所在的位置(互联网主机、你自己的root服务器等),也会有一些复杂的情况。

从@IKEA Riot中获得了windows服务和数据库标志的想法,您可以使用Quartz之类的东西。网还是城堡。Scheduler组件集成到您的网站中,或者被开发成一个单独的窗口服务,能够运行特定的作业。

Stackoverflow-石英网与asp网

这主要不是关于异步的,因为我读对了你的问题。这是一个长期运行的操作。您应该将长期运行的操作卸载到后台作业中。ASP.NET应用程序不适合执行后台作业。我可以看到你可以选择的几个选项:

  1. Windows服务-这可以轮询您的数据库以确定某个状态,并可以从该状态开始触发操作
  2. WCF服务-ASP.NET应用程序可以向WCF服务发送异步请求,而无需等待响应
  3. 可能还有其他类似BizTalk的应用程序,但这取决于您的应用程序的结构等

在UI中,您应该能够在计时器上轮询以向用户提供状态(如果您认为用户需要立即了解该状态),或者在操作完成时向用户发送电子邮件。

顺便说一句,使用asyn操作执行I/O很重要,其他操作已经提供了一些关于如何实现这一点的好链接。

如果您考虑用户体验(用户不应该等到这些任务执行完毕)和可靠性(即使应用程序重新启动,任务也必须排队并执行),您可以选择使用MSMQ。

MSMQ为Assinc操作提供了一个roubst解决方案。它支持同步操作,提供日志和托管的API,并将这种情况作为主要焦点进行检查。

看看这些文章:

MSMQ 简介

在.Net 中编程MSMQ

在更新实体时使用服务总线发布消息。这样,您不仅可以避免控制器中的长时间操作,而且还可以将控制器与发生某些事情时需要执行的操作解耦。

您还可以使用域事件来处理操作,并将其与您喜欢的ioc容器相结合:Advanced StructureMap:将实现连接到打开的泛型类型