C# 解耦(用于并行处理)web应用程序的最佳方法&x27;什么是非即时过程?

C# 解耦(用于并行处理)web应用程序的最佳方法&x27;什么是非即时过程?,c#,asp.net-mvc,parallel-processing,C#,Asp.net Mvc,Parallel Processing,我正在寻找一种好的策略,以真正地解耦web应用程序(ASP.NET MVC/C#)的非即时进程,实现并行处理。我将“非即时”定义为不需要立即执行的所有操作来呈现页面或更新信息 这些过程包括发送电子邮件、根据数据库信息更新一些内部统计数据、从web服务获取外部信息(只需要定期执行),等等 不过,主ASP.NETMVC应用程序和那些后台任务之间需要存在一些通信;e、 g.MVC应用程序需要通知电子邮件发送流程以发送内容 这样做的最佳策略是什么?MSMQ?将所有这些非即时进程转换为windows服务?

我正在寻找一种好的策略,以真正地解耦web应用程序(ASP.NET MVC/C#)的非即时进程,实现并行处理。我将“非即时”定义为不需要立即执行的所有操作来呈现页面或更新信息

这些过程包括发送电子邮件、根据数据库信息更新一些内部统计数据、从web服务获取外部信息(只需要定期执行),等等

不过,主ASP.NETMVC应用程序和那些后台任务之间需要存在一些通信;e、 g.MVC应用程序需要通知电子邮件发送流程以发送内容

这样做的最佳策略是什么?MSMQ?将所有这些非即时进程转换为windows服务?我正在想象一个真正解耦的场景,但我不希望这样做会使故障排除/单元测试变得更加困难或引入大量代码


谢谢大家!

我不能代表ASP.NET,因为我主要在Python中工作,但是……幸运的是,我可以回答这个问题,因为这更像是一个元语言问题

我通常使用一个独立运行的基于队列的后端守护进程来完成这项工作。当您需要向队列中添加某些内容时,可以使用您选择的方法(我偏爱HTTP)进行IPC并交付作业。守护进程只是一个接一个地完成作业——可能将它们委派给工作线程本身。您可以脱离应用程序的RESTful端,将作业发送到后端,即:

# In frontend (sorry for Python, should be clear)
...
backend_do_request("http://loadbalancer:7124/ipc", my_job)
...

# In backend (psuedoPython)
while 1:
   job = wait_for_request()
   myqueue.append(job)
...
def workerthread():
   job = myqueue.pop()
   do_job(job)
如果您以后需要检查后台守护进程并询问“2025年的工作完成了吗?”您可以在设计中说明这一点


如果你想用Windows服务做到这一点,我想你可以。它所需要做的就是在您选择的端口上监听您想要做的任何IPC——我坚持使用网络传输,因为本地IPC将使用相同的机器并限制您的可扩展性。你的单元测试不应该那么难;您可以将前端和后端视为两个不同的项目。

如果您可以为.NET 4 Framework开发,那么您可以使用F#或并行计算功能()

F#是为支持并行计算而设计的,因此它可能是比将代码转移到服务中更好的选择

尽管,如果您愿意,您可以只使用WCF并将所有内容卸载到Web服务,但这并不能真正解决您的问题,因为它只是将问题转移到其他地方

编辑:将非必要的内容移动到Web服务可能最有意义,这是一种标准做法,其中Web服务器位于防火墙之外,非常容易受到攻击,因此所有实际工作都由其他服务器完成,而Web服务器只负责静态页面和呈现

如果不想添加Web服务,您可以使用Spring.NET来实现这一点,但无论哪种方式,您基本上都只是调用一个远程进程来完成这项工作

这是可扩展的,因为您可以将业务逻辑分离到几个不同的服务器,而且由于Web服务器在很大程度上只是MVC的视图部分,因此它可以处理比所有MVC工作都在Web服务器中更多的请求


因为Spring.NET就是为此而设计的,所以它应该更容易测试,但是Web服务也可以测试,因为您应该单独测试每个部分,然后进行功能测试,但是,通过使用Spring.NET,模拟级别更容易。

我们已经通过工作流API完成了这项工作,或者,如果它不是必须执行的,您可以使用一个简单的delegate.BeginInvoke在后台线程上运行它。

在ASP.NET中处理异步处理的最简单方法是使用线程池创建一个工作线程,您可以将工作交给它。请注意,如果您有很多小任务要快速移交,那么默认线程池会有一些恼人的锁争用问题。在这种情况下,您要么需要使用C#4.0新的窃取线程池,要么可以使用具有窃取线程池实现的库(以及大量其他异步帮助程序),并与3.5配合使用。

这是一种我倾向于将其视为“脱机服务”的模式,我通常把它实现为一个Windows服务,可以按照自己的时间表运行多个任务

每个任务都实现了一个业务流程,例如从消息队列或数据库表发送挂起的电子邮件,将排队的日志消息写入底层提供程序,或者执行一些需要定期进行的批处理,例如归档旧数据或从传入提要导入数据对象

这种方法的优点是,您可以在任务管理服务中内置完整的管理功能,例如跟踪、模拟、通过WCF的远程集成以及错误处理和报告,同时使用您选择的.NET语言来实现任务本身

有一些调度API,比如Quartz.NET,可以用作此类系统的起点。就多线程而言,我的一般方法是在其自己的工作线程上运行每个任务,但只允许在给定时间运行任务的一个实例。如果一个任务需要并行执行,那么它将在任务体中实现,因为它将完全依赖于任务需要执行的工作


我认为web应用程序根本不应该管理这些任务,因为web应用程序的目的是处理用户的请求,而不是管理中间后台作业。最初构建这样的系统需要做大量的工作,但实际上您可以在任何项目上重复使用它。

我更喜欢使用windows服务,使用线程池管理这些任务,并通过MSMQ与之通信。由于具有公共队列功能,因此具有很好的可扩展性。

ThreadPool在.NET中是基于队列的工作池,但是ASP.NE在内部使用它
Table: JobQueue
JobID (bigint, auto number)
JobType (sendemail,calcstats)
JobParams (text)
IsRunning (true/false)
IsOver (true/false)
LastError (text)
class JobThread{
    static Thread bgThread = null;
    static AutoResetEvent arWait = new AutoResetEvent(false);

    public static void ProcessQueue(Job job)
    {
         // insert job in database
         job.InsertInDB();

         // start queue if its not created or if its in wait
         if(bgThread==null){
              bgThread = new Thread(new ..(WorkerProcess));
              bgThread.IsBackground = true;
              bgThread.Start();
         }
         else{
              arWait.Set();
         }
    }

    private static void WorkerProcess(object state){
         while(true){
              Job job = GetAvailableJob( 
                        IsProcessing = false and IsOver = flase);
              if(job == null){
                   arWait.WaitOne(10*1000);// wait ten seconds.
                                           // to increase performance
                                           // increase wait time
                   continue;
              }
              job.IsRunning = true;
              job.UpdateDB();
              try{

              //
              //depending upon job type do something...
              }
              catch(Exception ex){
                   job.LastError = ex.ToString(); // important step
                   // this will update your error in JobTable
                   // for later investigation
                   job.UpdateDB();
              }
              job.IsRunning = false;
              job.IsOver = true;
              job.UpdateDB();
         }
    }
}