在 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
条目不起作用,但这可能会改变。如果确实是这样,虽然您无法在这种代码中使用新的云优化框架,但您可能仍然可以在只打算在完整的.NET框架上运行的代码上使用AsyncTimeout
属性。
值得一提的是,这是我尝试添加到project.json
的内容。也许特定版本会让它更开心?
"frameworks": {
"net451": {
"dependencies": {
"System.Web.Mvc": ""
}
}
}
在“解决方案资源管理器”的引用列表中确实会出现对它的引用,但是会伴随着一个黄色的感叹号表示存在问题。只要这个引用还在,应用程序本身就会返回 500 错误。
Thread.Sleep(5000)
,如果将其更改为await Task.Delay(5000)
,那么它不会超时,因为它是异步的,不是一个非常通用的解决方案 :( - user1108069在WebAPI中,一般你会在客户端处理超时问题,而不是在服务器端。因为,正如我引用的话:
取消HTTP请求的方法是直接在HttpClient上取消它们。原因是,在单个HttpClient中,多个请求可以重用TCP连接,因此您不能安全地取消单个请求而不可能影响其他请求。
您可以控制请求的超时时间--如果我没记错的话,它在HttpClientHandler上。
如果你真的需要在API端本身实现超时,我建议创建一个线程来做你的工作,然后在一定时间后取消它。例如,你可以将它放在一个Task
中,使用Task.Wait
创建你的“超时”任务,并对第一个返回的任务使用Task.WaitAny
。这样就可以模拟超时了。
同样,如果你正在执行特定的操作,请检查它是否已经支持超时。很多时候,我会从我的WebAPI中执行一个HttpWebRequest
并指定它的Timeout属性。
Task.WaitAny
来实现,将主任务放在一个线程中,同时在另一个线程中创建一个“等待”超时任务。如果超时任务提前返回,那么你就可以立即返回。 - Mendhak对于每个需要超时的终结点,在其中通过管道传递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;
}
[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();
}