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

本文关键字:Async 创建 模式 -await 何创建 ActionResult | 更新日期: 2023-09-27 17:50:27

我有一个循环,它实际等待某个进程完成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秒内将中间响应发送回客户端,客户端将断开连接。

客户端长轮询的原因

我们的web服务器在高速互联网上,而其他客户端在低端连接上,从客户端到我们的服务器进行多个连接,然后进一步中继到第三方api在客户端几乎没有额外的开销。

这被称为Comet技术,而不是在持续5秒的时间内进行多个调用,保持连接打开的时间稍长一点可以减少资源消耗。

当然,如果客户端被断开连接,客户端将重新连接并再次等待。与单轮询请求

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

相比,每5秒多个HTTP连接消耗电池寿命更快。

首先,我应该指出SignalR的设计是为了取代手动长轮询。如果可能的话,我建议您先使用它。如果双方都支持,它会升级到WebSockets,这比长轮询更有效。

MVC中不支持"async ActionResult",但你可以通过一个技巧做一些类似的事情:

public async Task<ActionResult> Poll()
{
  while (!IsCompleted)
  {
    await Task.Delay(TimeSpan.FromSeconds(5));
    PartialView("PleaseWait").ExecuteResult(ControllerContext);
    Response.Flush();
  }
  return PartialView("Done");
}
然而,刷新部分结果完全违背了MVC的精神和设计。MVC =模型,视图,控制器。其中控制器构造模型并将其传递给视图。在这种情况下,你有控制器直接刷新视图的部分。

WebAPI有一个更自然,更少黑客的解决方案:PushStreamContent类型,与一个例子。

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

Task.Delay代替Thread.Sleep

await Task.Delay(5000);

Sleep告诉操作系统将线程置于睡眠状态,并将其从调度中删除至少5秒。如下所示,线程将在5秒内不做任何事情——这是你可以用来处理传入请求的少一个线程。

await Task.Delay创建一个计时器,它将在5秒后滴答。问题是,这个计时器本身不使用线程——它只是告诉操作系统在5秒后向ThreadPool线程发出信号。

同时,您的线程将可以自由地回答其他请求。


对于你的特定场景,似乎有一个陷阱。

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

看来你的选择是:

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

我有一个视频编码过程与第三方云api,然而我的web客户端(chrome/ie/ff)需要投票的编码结果。如果我简单地传递结果每5秒,web客户端将需要一个接一个地进行多个HTTP调用

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

在进行轮询时,客户端浏览器仍在等待您的HTTP响应。这样,客户端HTTP请求可能会超时。这也是一种不太友好的行为,用户不会得到任何进度通知,也不能请求取消。

我最近回答了一个关于长时间运行的服务器端操作的相关问题。在我看来,最好的处理方法是将其外包给WCF服务并使用AJAX轮询。我还回答了另一个有关如何在WCF服务中执行异步长轮询的相关问题。