超时Web Api请求
本文关键字:请求 Api Web 超时 | 更新日期: 2023-09-27 18:10:07
像MVC一样,WebApi运行在异步ASP上。. NET管道,意味着不支持执行超时。
在MVC中,我使用[AsyncTimeout]
过滤器,WebApi没有这个。那么如何在WebApi中超时一个请求呢?
基于Mendhak的建议,你可以做你想做的事情,尽管不完全是你想要的方式,而不需要跳过相当多的障碍。在不使用的情况下使用,过滤器可能看起来像这样:
public class ValuesController : ApiController
{
public async Task<HttpResponseMessage> Get( )
{
var work = this.ActualWork( 5000 );
var timeout = this.Timeout( 2000 );
var finishedTask = await Task.WhenAny( timeout, work );
if( finishedTask == timeout )
{
return this.Request.CreateResponse( HttpStatusCode.RequestTimeout );
}
else
{
return this.Request.CreateResponse( HttpStatusCode.OK, work.Result );
}
}
private async Task<string> ActualWork( int sleepTime )
{
await Task.Delay( sleepTime );
return "work results";
}
private async Task Timeout( int timeoutValue )
{
await Task.Delay( timeoutValue );
}
}
这里你将收到一个超时,因为我们正在做的实际"工作"将花费比超时更长的时间。
用属性做你想做的是可能的,尽管不是理想的。这是与前面相同的基本思想,但是过滤器实际上可以用来通过反射执行动作。我认为我不会推荐这种方式,但是在这个人为的例子中,您可以看到它是如何实现的:
public class TimeoutFilter : ActionFilterAttribute
{
public int Timeout { get; set; }
public TimeoutFilter( )
{
this.Timeout = int.MaxValue;
}
public TimeoutFilter( int timeout )
{
this.Timeout = timeout;
}
public override async Task OnActionExecutingAsync( HttpActionContext actionContext, CancellationToken cancellationToken )
{
var controller = actionContext.ControllerContext.Controller;
var controllerType = controller.GetType( );
var action = controllerType.GetMethod( actionContext.ActionDescriptor.ActionName );
var tokenSource = new CancellationTokenSource( );
var timeout = this.TimeoutTask( this.Timeout );
object result = null;
var work = Task.Run( ( ) =>
{
result = action.Invoke( controller, actionContext.ActionArguments.Values.ToArray( ) );
}, tokenSource.Token );
var finishedTask = await Task.WhenAny( timeout, work );
if( finishedTask == timeout )
{
tokenSource.Cancel( );
actionContext.Response = actionContext.Request.CreateResponse( HttpStatusCode.RequestTimeout );
}
else
{
actionContext.Response = actionContext.Request.CreateResponse( HttpStatusCode.OK, result );
}
}
private async Task TimeoutTask( int timeoutValue )
{
await Task.Delay( timeoutValue );
}
}
可以这样使用:
[TimeoutFilter( 10000 )]
public string Get( )
{
Thread.Sleep( 5000 );
return "Results";
}
这适用于简单类型(例如字符串),在Firefox中给出:<z:anyType i:type="d1p1:string">Results</z:anyType>
,尽管您可以看到,序列化并不理想。在这段代码中使用自定义类型在序列化过程中可能会有一些问题,但经过一些工作,这在某些特定场景中可能会很有用。操作参数以字典而不是数组的形式出现,这也可能在参数排序方面带来一些问题。显然,有真正的支持会更好。
就vNext而言,他们很可能计划为Web API添加服务器端超时的能力,因为MVC和API控制器是统一的。如果他们这样做,很可能不会通过System.Web.Mvc.AsyncTimeoutAttribute
类,因为他们显式地删除了对System.Web
的依赖。
到目前为止,在project.json
文件中添加System.Web.Mvc
条目似乎不可行,但这很可能会改变。如果是这样,虽然你不能使用新的云优化框架与这样的代码,你可能能够使用AsyncTimeout
属性的代码,只打算与完整的。net框架运行。
对于它的价值,这是我试图添加到project.json
。也许一个特定的版本会让它更快乐?
"frameworks": {
"net451": {
"dependencies": {
"System.Web.Mvc": ""
}
}
}
对它的引用确实显示在解决方案资源管理器的引用列表中,但是它使用黄色感叹号表示存在问题。应用程序本身返回500个错误,而这个引用仍然存在。
使用WebAPI,您通常会在客户端端处理超时,而不是在服务器端。这是因为,我引用:
取消HTTP请求的方法是直接在HttpClient上取消它们。原因是多个请求可以在单个HttpClient中重用TCP连接,因此您无法在不影响其他请求的情况下安全地取消单个请求。
你可以控制请求的超时时间——如果我没记错的话,我想它是在HttpClientHandler上的。
如果你真的需要在API端实现超时,我建议创建一个线程来完成你的工作,然后在一段时间后取消它。例如,您可以将其放在Task
中,使用Task.Wait
创建"超时"任务,并使用Task.WaitAny
作为第一个返回的任务。这可以模拟超时。
同样,如果您正在执行一个特定的操作,请检查它是否已经支持超时。通常,我会从我的WebAPI执行HttpWebRequest
,并指定其Timeout属性。
对于你想要超时的每个端点,通过一个CancellationToken
管道,例如:
[HttpGet]
public Task<Response> GetAsync()
{
var tokenSource = new CancellationTokenSource(_timeoutInSec * 1000);
return GetResponseAsync(tokenSource.Token);
}
让您的生活更轻松,在您的基础控制器中添加以下方法:
protected async Task<T> RunTask<T>(Task<T> action, int timeout) {
var timeoutTask = Task.Delay(timeout);
var firstTaskFinished = await Task.WhenAny(timeoutTask, action);
if (firstTaskFinished == timeoutTask) {
throw new Exception("Timeout");
}
return action.Result;
}
现在每个从你的基控制器继承的控制器都可以访问RunTask方法。现在在API中像这样调用RunTask方法:
[HttpPost]
public async Task<ResponseModel> MyAPI(RequestModel request) {
try {
return await RunTask(Action(), Timeout);
} catch (Exception e) {
return null;
}
}
private async Task<ResponseModel> Action() {
return new ResponseModel();
}