21

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?

greatwolf
  • 20,287
  • 13
  • 71
  • 105
leora
  • 188,729
  • 360
  • 878
  • 1,366
  • 5
    You may want to consider off loading that work (email and reports) to a seperate service. – IKEA Riot Jun 06 '11 at 10:47
  • @IKEA Riot - but how would i call into that . .the code to generate the emails and reports are in the website controller . . – leora Jun 06 '11 at 10:59
  • 3
    In an application I worked on, I wrote a flag back to the database. A seperate program, in this case a system service, sits in the background, polling the database looking for work. – IKEA Riot Jun 06 '11 at 12:38

9 Answers9

51

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.

RPM1984
  • 72,246
  • 58
  • 225
  • 350
  • 3
    @RPM1984 - so, to clarify, you are NOT waiting for the tasks to finish before returning the view . .correct ?? – leora Jun 15 '11 at 16:58
  • @RPM1984 - so thanks . . i didn't realize you were able to do this. i thought this might break the "pipeline" and go out of scope in some sense – leora Jun 16 '11 at 09:21
  • @ooo - it goes "out of scope" from the perspective of the original thread that is executing the HTTP request (eg invoking the action). A seperate thread is fired. As i said, there is a lot more you can do with `Task.Factory` to synchronize tasks, wait for callbacks, etc - so if the above simple example doesn't suit you can read up more on MSDN. – RPM1984 Jun 16 '11 at 10:20
  • @RPM1984 - i think this does do what i want but i just wanted to make sure.. does this require .net 4.0 (as i am using 3.5 now) – leora Jun 16 '11 at 10:47
  • @ooo - yep, it's 4.0. If your stuck on 3.5 you can use Threadpool: http://msdn.microsoft.com/en-us/library/system.threading.threadpool.aspx – RPM1984 Jun 16 '11 at 11:37
  • @RPM1984 - i will upgrade to 4. 0 now :) – leora Jun 16 '11 at 14:21
  • Asynchronous controllers free up threads for other requests whilst work in being performed, and prevent blocking other requests into the system. However the request to a asynchronous controller **will** still have to wait until the operation completes. If you want to return immediately, just do the work in a background thread. – TheCodeKing Sep 01 '11 at 13:42
  • 5
    Mark this as an answer. RPM1884 deservses the bounty – Wouter Janssens Sep 02 '11 at 09:54
  • How do you detect if the tasks failed to complete or not? – cpoDesign Sep 04 '11 at 10:09
  • 2
    @cpoDesign - in the example i provided, you cannot. But with a little more code, you can, as i said in my above comments. – RPM1984 Sep 05 '11 at 00:00
  • Does not work anymore in 4.5. You'll get `An asynchronous operation cannot be started at this time.` error. – Kugel Sep 17 '13 at 23:35
16

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);
}
TheCodeKing
  • 19,064
  • 3
  • 47
  • 70
5

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.

archil
  • 39,013
  • 7
  • 65
  • 82
  • thanks for your response. can you clarify how you can both return a value to a user as well as keep the async operation running – leora Jun 06 '11 at 11:47
  • The best way would be as @Paolo Moretti adviced. Controller should be tiny, and only update model. Than some other service in application, maybe not web application at all - windows service or some scheduling service could look at the updated model and do its task. There're really too many ways to do this kind of things - and they depend on your concrete system. But first of all, forget that controller action should perform this work. Just think of this process as separate between triggering long running task and executing it. Trigger by controller. Execute by other service – archil Jun 06 '11 at 11:55
  • are you implying that this "service" code must be outside my website or simply another assembly or class? Is there any solution that allows me to run the code from my website (so i don't have the overhead of managing a separate and independent service just for running what is essentially just an expensive method) – leora Jun 06 '11 at 11:58
  • Ok, as i said everything depends on your system. I would handle the overhead of separate service for that task if i didn't want user to wait for my request. Simplest method is to just start the new Thread() and return result to user. And in that thread do your job. But once again, you should take deep attention to details with this task running - what if excepion occures, some subtasks fail, etc. Thread t = new Thread(ExecuteLongAction); t.Start(); return Content("Update Submitted"); //this would start ExecuteLongAction method but return responce to user until method execution finishes – archil Jun 06 '11 at 12:22
3

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.

Martin Brabec
  • 3,720
  • 2
  • 23
  • 26
  • Are you saying that the accepted answer is not correct any more? – leora Mar 26 '15 at 11:04
  • No, it is correct. My answer is just newer (and more elegant I think) option for solving long running tasks inside a web application :-) – Martin Brabec Mar 28 '15 at 11:06
  • 1
    @LyubomirVelchev It is open source and it is free to use the basic features. For more advanced scenarios you might need to upgrade to the premium edition. – hbulens Aug 04 '16 at 09:49
2

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.

Eddie Butt
  • 493
  • 3
  • 4
1

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

Community
  • 1
  • 1
Nathan Fisher
  • 7,961
  • 3
  • 47
  • 68
1

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.

Charles Prakash Dasari
  • 4,964
  • 1
  • 27
  • 46
1

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

Adilson de Almeida Jr
  • 2,761
  • 21
  • 37
0

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

Giorgi
  • 30,270
  • 13
  • 89
  • 125