简体   繁体   中英

In asp.net-mvc, how can I run an expensive operation without slowing down user experience?

I have an asp.net-mvc website and I am using nhibernate for my ORM.

I have a current controller action that does a basic CRUD update (queries an item from the database and then updates a bunch of values and commits back to the db table). It then returns a simple json response to the client to indicate success or error.

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

In certain cases (assuming success of commit and if certain fields are changed in my object) I now want to trigger some additional actions (like emailing a bunch of people and running some code that generates a report) but I don't want to slow down the user experience of the person that is doing the update. So my concern is that if I did this:

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

that it would be too slow for the user experience of someone updating. Is there anyway (asyngc?) to have an expensive operation get triggered off of a controller action but not have that controller action slowed down because of it?

I've done this before.

The most robust way would be to use Asynchronous Controller's, or better yet an independant service such as a WCF service.

But in my experience, i've just needed to do "simple", one-liner task, such as auditing or reporting, as you say.

In that example, the easy way - fire off a Task :

public ActionResult Do()
{
    SomethingImportantThatNeedsToBeSynchronous();

    Task.Factory.StartNew(() => 
    {
       AuditThatTheUserShouldntCareOrWaitFor();
       SomeOtherOperationTheUserDoesntCareAbout();
    });

    return View();

}

That's a simple example. You can fire off as many tasks as you want, synchronize them, get notified when they finish, etc.

I've currently used the above to do Amazon S3 uploading.

If the intention is to return JSON immediately, and leave the intensive work to run asynchronously in the background without affecting the user experience, then you need to start a new background thread. AsyncController won't help you here.

There are lots of arguments about starving the thread request pool by doing this (which is true), but the counter argument is you should starve the pool because the server is busy doing work. Of course ideally you should move the work off onto another server entirely via queuing/distributed systems etc, but that's a complex solution. Unless you need to handle hundreds of request you do not need to consider this option, as it'a unlikely it would ever cause an issue. It really depends of the scalability requirements of your solution, how long the background process is going to take, and how often it is called. The simplest solution is as follows:

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);
}

You should do Asynchronous operations and asynchronous controllers to not lock the thread pool and not to make other users of web site suffer. When task is running long, the thread taken from asp.net thread pool is reserved and not returned to pool until operation is complete. If there will be many simultaneous long running tasks, many threads will be reserved by them, so there's big chance other users that visit your site suffer by waiting. ASYNC OPERATIONS WILL NOT MAKE ANY CODE FASTER. I Advice you to use async controllers just for threads case i wrote about above, but that is not enough. I think you should use some links or ajax to trigger operation on server and let user continue his surfing on site. Once operation is finished, on next page refresh, user should be notified that task has finished executing. And here's another proof that business codes should not be written in controller. You should have separated services for that.

This is an old question, so I belive it needs update.

I recommend using HangFire ( http://hangfire.io ). With this, you can simply enqueue your job even in web application . HangFire will make sure the job will run at least once.

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

You can also watch status of all queued jobs in nice UI.

I think that you really want a Worker Role akin to the Windows Azure one.

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

I am not sure how to best implement that in pure MVC without the Azure queueing. And, depending on where you are hosting (internet hoster, your own server with root, etc.) there are complications.

Taking the idea from @IKEA Riot of the the windows service and a database flag you could use something like Quartz.Net or the Castle.Scheduler component which intergrates into your website or be developed into a seperate windows service that is able to run specific jobs.

Stackoverflow - quartz-net-with-asp-net

This is not mainly about asynchronous as I read your question correctly. This is about a long running operation. You should offload the long running operation into background jobs. ASP.NET apps are not suitable to execute background jobs. I can see a couple of options that you can go with:

  1. Windows Service - this can poll your DB for a certain state and can trigger an action from that state onwards
  2. WCF service - your ASP.NET application can send an asynchronous request to the WCF service without waiting for the response
  3. There could be others like BizTalk, but it depends on how your app is structured etc.

From the UI, you should be able to poll on a timer to provide the status to the user (if you think the user need to know that status immediately), or send an email to the user when the action is complete.

On a side note, it is important to perform your I/O using asyn operations and others have already provided some good links on how you can accomplish that.

IF you are thinking about user experience (the user shouldn't wait until theses tasks are performed) and reliability (the tasks must be enqueued and executed even if the application reinit), you can choose to use MSMQ.

MSMQ provides a roubst solution for assync operations. It supports sync operations, provides logs and a managed API, and has as main focus exaclty this kind of situation.

Take a look at these articles:

Introduction to MSMQ

Programming MSMQ in.Net

Use a service bus to publish a message when an entity is updated. This way you will not only avoid lengthy operations in controllers but you will be also able to decouple controllers from the actions that need to be performed when something happens.

You can also use Domain Events to process the actions and combine it with your favorite ioc container: Advanced StructureMap: connecting implementations to open generic types

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM