如何创建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秒的时间内进行多个调用,保持连接打开的时间稍长一点可以减少资源消耗。
当然,如果客户端被断开连接,客户端将重新连接并再次等待。与单轮询请求
首先,我应该指出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服务中执行异步长轮询的相关问题。