与MVC一样,WebApi在异步ASP.NET管道上运行,意思是execution timeout is unsupported。
在MVC中我使用[AsyncTimeout]
过滤器,WebApi没有这个。那么如何在WebApi中超时请求?
答案 0 :(得分:17)
在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 );
}
}
在这里,您将收到超时,因为实际的&#34;工作&#34;我们做的事情需要的时间超过超时时间。
要做你想要的 属性是可能的,虽然不理想。它与以前的基本思想相同,但过滤器实际上可用于通过反射执行操作。我不认为我会推荐这条路线,但在这个人为的例子中,你可以看到它是如何完成的:
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而言,由于MVC和API控制器正在统一,他们可能正计划添加为Web API执行服务器端超时的功能。如果他们这样做,则可能不会通过System.Web.Mvc.AsyncTimeoutAttribute
类,因为他们明确删除System.Web
上的依赖项。
截至今天,似乎没有向System.Web.Mvc
文件添加project.json
条目,但这可能会发生变化。如果确实如此,虽然您无法将新的云优化框架与此类代码一起使用,但您可以在仅用于运行完整代码的代码上使用AsyncTimeout
属性。 NET框架。
对于它的价值,这是我尝试添加到project.json
的内容。也许特定的版本会让它更快乐?
"frameworks": {
"net451": {
"dependencies": {
"System.Web.Mvc": ""
}
}
}
对它的引用确实会显示在解决方案资源管理器的引用列表中,但它会显示一个黄色感叹号,表示存在问题。应用程序本身返回500个错误,而此引用仍然存在。
答案 1 :(得分:10)
使用WebAPI,您通常会在客户端端而不是服务器端处理超时。这是因为,和I quote:
取消HTTP请求的方法是直接在HttpClient上取消它们。原因是多个请求可以在单个HttpClient中重用TCP连接,因此您无法安全地取消单个请求,也不会影响其他请求。
你可以控制请求的超时 - 如果我没记错的话,我认为它在HttpClientHandler上。
如果你真的需要在API方面实现超时,我建议创建一个线程来完成你的工作,然后在一段时间后取消它。例如,您可以将其放在Task
中,创建“超时”状态。任务使用Task.Wait
并使用Task.WaitAny
让第一个回来。这可以模拟超时。
同样,如果您正在执行特定操作,请检查它是否已支持超时。通常,我会从我的WebAPI执行HttpWebRequest
,并指定其Timeout属性。
答案 2 :(得分:2)
对于要超时的每个端点,请通过以下方式传递CancellationToken
:
[HttpGet]
public Task<Response> GetAsync()
{
var tokenSource = new CancellationTokenSource(_timeoutInSec * 1000);
return GetResponseAsync(tokenSource.Token);
}
答案 3 :(得分:0)
让您的生活更轻松,在您的基本控制器中添加以下方法:
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();
}