3

I have a loop that actually waits for some process for completion of a Job and returns result.

I have MyRestClient.FetchResult(id) and MyRestClient.FetchResultAsync(id) both available to me, which fetches result from some remote service and returns boolean value if it is complete.

 public class StatusController: ActionController {

    public ActionResult Poll(long id){
        return new PollingResult(()=>{
            return MyRestClient.FetchResult(id) == SomethingSuccessful;
        });
    }
 }

 public class PollingResult : ActionResult{

     private Func<bool> PollResult;

     public PollingResult(Func<bool> pollResult){
         this.PollResult = pollResult;
     }

     public override void ExecuteResult(ControllerContext context)
     {
         Response = context.HttpContext.Response;
         Request = context.HttpContext.Request;

         // poll every 5 Seconds, for 5 minutes
         for(int i=0;i<60;i++){
             if(!Request.IsClientConnected){
                 return;
             }
             Thread.Sleep(5000);
             if(PollResult()){
                  Response.WriteLine("Success");
                  return;
             }

             // This is a comet, so we need to 
             // send a response, so that browser does not disconnect
             Response.WriteLine("Waiting");
             Response.Flush();
         }

         Response.WriteLine("Timeout");

     }
 }

Now I am just wondering if there is anyway to use Async Await to improve this logic because this thread is just waiting for every 5 seconds for 5 minutes.

Update

Async Task pattern usually finishes all work before sending result back to client, please note, if I do not send intermediate responses back to client in 5 seconds, client will disconnect.

Reason for Client Side Long Poll

Our web server is on high speed internet, where else clients are on low end connection, making multiple connections from client to our server and then relaying further to third party api is little extra overhead on client end.

This is called Comet technology, instead of making multiple calls in duration of 5 seconds, keeping a connection open for little longer is less resource consuming.

And of course, if client is disconnected, client will reconnect and once again wait. Multiple HTTP connections every 5 seconds drains battery life quicker compared to single polling request

Akash Kava
  • 39,066
  • 20
  • 121
  • 167
  • Where is that thread running? Is it a ThreadPool thread? Is this work being done in the background of an ASP.NET application? – dcastro Mar 15 '14 at 11:25
  • ActionResult is executed by ASP.NET worker thread pool, I am not creating any extra thread. – Akash Kava Mar 15 '14 at 11:36
  • Do you actually have some sort of external event which you might used instead of polling? Usually there's one. If so, check this: http://stackoverflow.com/q/22342967/1768303 – noseratio Mar 15 '14 at 12:05
  • @Noseratio I have a video encoding in process with third party cloud api, however my web client (chrome/ie/ff) need to poll result of encoding. If I simply pass on result for every 5 seconds, web client will need to make multiple HTTP calls one after another, – Akash Kava Mar 15 '14 at 12:27

3 Answers3

12

First, I should point out that SignalR was designed to replace manual long-polling. I recommend that you use it first, if possible. It will upgrade to WebSockets if both sides support it, which is more efficient than long polling.

There is no "async ActionResult" supported in MVC, but you can do something similar via a trick:

public async Task<ActionResult> Poll()
{
  while (!IsCompleted)
  {
    await Task.Delay(TimeSpan.FromSeconds(5));
    PartialView("PleaseWait").ExecuteResult(ControllerContext);
    Response.Flush();
  }

  return PartialView("Done");
}

However, flushing partial results goes completely against the spirit and design of MVC. MVC = Model, View, Controller, you know. Where the Controller constructs the Model and passes it to the View. In this case you have the Controller is directly flushing parts of the View.

WebAPI has a more natural and less hackish solution: a PushStreamContent type, with an example.

MVC was definitely not designed for this. WebAPI supports it but not as a mainstream option. SignalR is the appropriate technology to use, if your clients can use it.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
5

Use Task.Delay instead of Thread.Sleep

await Task.Delay(5000);

Sleep tells the operating system to put your thread to sleep, and remove it from scheduling for at least 5 seconds. As follows, the thread will do nothing for 5 secs - that's one less thread you can use to process incoming requests.

await Task.Delay creates a timer, which will tick after 5 seconds. The thing is, this timer doesn't use a thread itself - it simply tells the operating system to signal a ThreadPool thread when 5 seconds have passed.

Meanwhile, your thread will be free to answer other requests.


update

For your specific scenario, it seems there's a gotcha.

Normally, you'd change the surrounding method's signature to return a Task/Task<T> instead of void. But ASP.NET MVC doesn't support an asynchronous ActionResult (see here).

It seems your options are to either:

  • move the async code to the controller (or to another class with an async-compatible interface)
  • Use a WebAPI controller, which seems to be a good fit for your scenario.
Community
  • 1
  • 1
dcastro
  • 66,540
  • 21
  • 145
  • 155
  • Async controller or WebAPI does not allow flushing in between. Usually in MVC after controller has finished executing action, action result is invoked which sends the result. Purpose of creating PollingResult is, it is flushing partial response. – Akash Kava Mar 15 '14 at 12:25
  • @AkashKava To be honest, I'm not too familiar with the MVC framework.. I suggest your ask another question, something along the lines of "Alternatives to async ActionResult". I hope to have helped. – dcastro Mar 15 '14 at 12:40
  • 3
    @AkashKava Why not create a new one? Now our answers don't fit the question. You should leave it as it was, and create a new question. You wanted to know how async/await could benefit your logic - I've answered that, and now you have another follow-up question. – dcastro Mar 15 '14 at 12:53
1

I have a video encoding in process with third party cloud api, however my web client (chrome/ie/ff) need to poll result of encoding. If I simply pass on result for every 5 seconds, web client will need to make multiple HTTP calls one after another

I think the approach when you're trying to poll the result of the video encoding operation within the boundaries of a single HTTP request (i.e., within your ASP.NET MVC controller method) is wrong.

While you're doing the polling, the client browser is still waiting for your HTTP response. This way, the client-side HTTP request may simple get timed out. It is also a not-so-user-friendly behavior, the user is not getting any progress notifications, and cannot request the cancellation.

I've recently answer a related question about long-running server side operation. IMO, the best way of dealing with it is to outsource it to a WCF service and use AJAX polling. I also answered another related question on how to do the asynchronous long-polling in a WCF service.

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • 2
    @AkashKava In all fairness, he didn't know that polling was a strict requirement. – dcastro Mar 15 '14 at 12:47
  • 1
    @AkashKava, you keep updating your question with your requirements too often. And where did I suggest polling from the client every 5 seconds? Good luck with your research. – noseratio Mar 15 '14 at 13:26