Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create Async ActionResult to create async-await pattern for following polling logic?

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

like image 842
Akash Kava Avatar asked Oct 20 '25 16:10

Akash Kava


2 Answers

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.

like image 91
Stephen Cleary Avatar answered Oct 23 '25 07:10

Stephen Cleary


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.
like image 35
dcastro Avatar answered Oct 23 '25 08:10

dcastro