如何创建Async ActionResult来为以下轮询逻辑创建async-await模式?

时间:2014-03-15 11:15:12

标签: c# asp.net-mvc asynchronous async-await

我有一个循环,实际上等待一些完成Job的过程并返回结果。

我可以使用MyRestClient.FetchResult(id)MyRestClient.FetchResultAsync(id),它从某些远程服务获取结果,如果完成则返回布尔值。

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

     }
 }

现在我只是想知道是否还有使用Async Await来改进这个逻辑,因为这个线程只是每5秒等待5分钟。

更新

异步任务模式通常在将结果发送回客户端之前完成所有工作,请注意,如果我在5秒内没有将中间响应发送回客户端,则客户端将断开连接。

客户端长轮询的原因

我们的网络服务器位于高速互联网上,其他客户端处于低端连接,从客户端到我们的服务器进行多次连接,然后进一步转发到第三方api,这对客户端来说几乎没有额外开销。

这称为Comet技术,而不是在5秒的持续时间内进行多次调用,保持连接打开的时间更长,资源消耗更少。

当然,如果客户端断开连接,客户端将重新连接并再次等待。与单个轮询请求相比,每5秒多个HTTP连接可以更快地耗尽电池寿命

3 个答案:

答案 0 :(得分:6)

首先,我应该指出SignalR旨在取代手动长轮询。如果可能,我建议您先使用它。如果双方都支持它,它将升级到WebSockets,这比长轮询更有效。

没有&#34; async ActionResult&#34;在MVC中受支持,但您可以执行类似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");
}

然而,冲洗部分结果完全违背了MVC的精神和设计。 MVC =模型,视图,控制器,你知道。 Controller构造Model并将其传递给View。在这种情况下,您可以让Controller直接刷新View的部分内容。

WebAPI有一个更自然,更少hackish的解决方案:PushStreamContent类型,with an example

MVC绝对不是为此而设计的。 WebAPI支持它,但不是主流选项。如果您的客户可以使用SignalR,SignalR是适当的技术。

答案 1 :(得分:4)

使用Task.Delay代替Thread.Sleep

await Task.Delay(5000);

Sleep告诉操作系统让你的线程进入休眠状态,并将其从调度中移除至少5秒钟。如下所示,该线程将在5秒内不执行任何操作 - 这可以用于处理传入请求的线程少一个。

await Task.Delay创建一个计时器,该计时器将在5秒后打勾。问题是,这个计时器doesn't use a thread itself - 它只是告诉操作系统在5秒钟后发出一个ThreadPool线程的信号。

同时,您的主题可以自由回答其他请求。


<强>更新

对于您的特定情况,似乎有一个问题。

通常,您更改周围方法的签名以返回Task / Task<T>而不是void。但ASP.NET MVC不支持异步ActionResult(请参阅here)。

您似乎可以选择:

  • 将异步代码移动到控制器(或带有异步兼容接口的另一个类)
  • 使用WebAPI控制器,这似乎非常适合您的场景。

答案 2 :(得分:1)

  

然而,我正在使用第三方云API进行视频编码   我的网络客户端(chrome / ie / ff)需要轮询编码结果。如果我   只需每隔5秒传递一次结果,Web客户端就需要   一个接一个地进行多个HTTP调用

我认为当您尝试在单个HTTP请求的边界内(即在ASP.NET MVC控制器方法中)轮询视频编码操作的结果时,这种方法是错误的。

在进行轮询时,客户端浏览器仍在等待您的HTTP响应。这样,客户端HTTP请求可能很容易超时。它也是一种不那么用户友好的行为,用户没有收到任何进度通知,也无法请求取消。

我最近回答了一个关于long-running server side operation的相关问题。 IMO,处理它的最佳方法是将其外包给WCF服务并使用AJAX轮询。我还在how to do the asynchronous long-polling in a WCF service上回答了另一个相关问题。