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

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

我有一个当前的控制器操作,它执行基本的CRUD更新(从数据库中查询项目,然后更新一堆值并提交回db表)。 然后它向客户端返回一个简单的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?)有一个昂贵的操作被控制器动作触发但是没有控制器动作因此而减慢?

我之前做过这个。

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

但根据我的经验,我只需要做“简单”的单行任务,例如审计或报告,如你所说。

在那个例子中,简单的方法 – 启动Task

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

这是一个简单的例子。 您可以根据需要启动任意数量的任务,同步它们,在完成时获得通知等。

我目前使用上面的方法来进行Amazon S3上传。

如果打算立即返回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线程池获取的线程被保留,并且在操作完成之前不会返回到池。 如果同时存在许多长时间运行的任务,那么他们将保留许multithreading,因此访问您站点的其他用户很可能会因等待而受到影响。 ASYNC的操作不会使任何代码更快。 我建议你使用异步控制器只是为了我上面写的线程案例,但这还不够。 我认为你应该使用一些链接或ajax来触发服务器上的操作,让用户继续在网站上冲浪。 操作完成后,在下一页刷新时,应通知用户任务已完成执行。 这是另一个certificate商业代码不应该写在控制器中的certificate。 你应该为此分开服务。

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

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

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

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

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

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

我不确定如何在没有Azure排队的情况下在纯MVC中最好地实现它。 而且,根据您托管的位置(互联网托管服务商,您自己的root服务器等),有并发症。

从@IKEA Riot的Windows服务和数据库标志中获取想法,您可以使用Quartz.Net或Castle.Scheduler组件,它们可以集成到您的网站中,或者开发成能够运行特定作业的单独Windows服务。

Stackoverflow – 石英网与asp-net

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

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

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

另一方面,使用asyn操作执行I / O非常重要,而其他人已经就如何实现这一点提供了一些很好的链接。

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

MSMQ为同步操作提供最佳解决方案。 它支持同步操作,提供日志和托管API,并且主要关注这种情况。

看看这些文章:

MSMQ简介

在.Net中编程MSMQ

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

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