4

I'm interested in running a long process, I want to update the UI as soon as results start coming in and not wait for it to finish.

How would I go about this? I've read about Async controllers but they don't seem to have anything built-in for this purpose.

Just save the results in the Application/Session object as the results come in and use polling from client side? I can think of several ways that could go wrong (like user closes page, and the object stays in Application object forever - need to manage expiration of these object myself, extra server-load for polling, etc).

Any thoughts?

Thank you

bryanmac
  • 38,941
  • 11
  • 91
  • 99
Madd0g
  • 3,841
  • 5
  • 37
  • 59

3 Answers3

5

I was trying to solve something similar (reporting real-time progress from a long running server operation back to the client) recently and it turned out SignalR is a perfect fit for this situation.

Basically it is a library wrapping long-polling and Web Sockets, using (transparently) whatever is available on server and client.

I only have good experience with it so far.

twoflower
  • 6,788
  • 2
  • 33
  • 44
1

You can run the long running task using ThreadPool.QueueUserWorkItem and it can update state in Application/Session which the user can poll. As you pointed that has some lifetime issues. Beyond what you pointed out, the app-pool could recycle - but maybe that's OK.

What is the logical scope of that operation? Is it a system type long running task that someone/many folks need to query the progress on? Or is it a long running task on behalf of a specific user? If it's the latter, then it's OK if that user's session times out, etc... If it's the former then you need to be more durable. for example, you could store the task request, the state and the progress in a database. That way on app restart it can just pickup where it eft off and its easy to query by anyone (a noter decision point if system level task).

The final consideration is whether you'll ever have more than one web roles (web farm/cluster). If that's ever a consideration than a DB or even separate worker role/service becomes more appropriate.

So it all boils down to the type of task it is, who needs to monitor it and what are the durability requirements. If its just a user task, keep it simple, queueuserworkitem and session state.

bryanmac
  • 38,941
  • 11
  • 91
  • 99
  • Just on a side note, when doing polling, you can deduct that when the client stops polling, he left eh page. This requires some background-worker, but it's still doable, I once made a chat like that. – Alxandr Nov 05 '11 at 12:59
  • Thanks, it is a user-initiated task, it usually takes less than 15 seconds, but initial results could come in after a second or two, but doing the initial polling is what troubles me - if I want to keep the UI really responsive - I'd have to poll quickly and a lot, if I just sleep the ajax result until first results arrive - would that be considered a good practice for my situation? – Madd0g Nov 05 '11 at 13:09
0

This article seems to describe what you want, simple and w/o SignalR:

ASP.NET MVC 3: Async jQuery progress indicator for long running tasks

Controller:

public class HomeController : Controller { private static IDictionary tasks = new Dictionary();

 public ActionResult Index()
 {
   return View();
 }

 public ActionResult Start()
 {
   var taskId = Guid.NewGuid();
   tasks.Add(taskId, 0);

   Task.Factory.StartNew(() =>
   {
     for (var i = 0; i <= 100; i++)
     {
       tasks[taskId] = i; // update task progress
       Thread.Sleep(50); // simulate long running operation
     }
     tasks.Remove(taskId);
   });

   return Json(taskId);
 }

 public ActionResult Progress(Guid id)
 {
   return Json(tasks.Keys.Contains(id) ? tasks[id] : 100);
 }

}

View:

<script type="text/javascript">

function updateMonitor(taskId, status) {
  $("#" + taskId).html("Task [" + taskId + "]: " + status);
}

$(function () {
  $("#start").click(function (e) {
   e.preventDefault();
   $.post("Home/Start", {}, function (taskId) {

     // Init monitors
     $("#monitors").append($("<p id='" + taskId + "'/>"));
     updateMonitor(taskId, "Started");

     // Periodically update monitors
     var intervalId = setInterval(function () {
       $.post("Home/Progress", { id: taskId }, function (progress) {
         if (progress >= 100) {
           updateMonitor(taskId, "Completed");
         clearInterval(intervalId);
         } else {
           updateMonitor(taskId, progress + "%");
         }
       });
     }, 100);
   });
 });

});

Jeroen K
  • 10,258
  • 5
  • 41
  • 40