Web Api请求超时? [英] Timeout a Web Api request?

查看:1025
本文介绍了Web Api请求超时?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

像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而言,由于MVC和API控制器是统一的,因此他们很可能计划增加对Web API进行服务器端超时的功能.如果这样做,则可能不会通过System.Web.Mvc.AsyncTimeoutAttribute类,因为他们明确删除了对System.Web的依赖关系.

到今天为止,在project.json文件中添加System.Web.Mvc条目似乎还行得通,但这可能会改变.如果是这样,虽然您将无法通过此类代码使用新的针对云进行了优化的框架,但您可能能够在仅与完整的.NET框架一起运行的代码上使用AsyncTimeout属性./p>

对于它的价值,这是我尝试添加到project.json中的内容.也许特定的版本会让它更快乐?

"frameworks": {
    "net451": {
        "dependencies": { 
            "System.Web.Mvc": ""
        }
    }
}

对它的引用确实显示在解决方案资源管理器的引用列表中,但是它带有黄色的感叹号表示问题.在保留此引用的同时,应用程序本身返回500个错误.

Like MVC WebApi runs on the asynchronous ASP.NET pipeline, meaning execution timeout is unsupported.

In MVC I use the [AsyncTimeout] filter, WebApi doesn't have this. So how do I timeout a request in WebApi?

解决方案

Building on the suggestion by Mendhak, it is possible to do what you want, though not exactly the way you'd like to do it without jumping through quite a few hoops. Doing it without a filter might look something like this:

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 );
    }
}

Here you will receive a timeout because the actual "work" we're doing will take longer than the timeout.

To do what you want with an attribute is possible, though not ideal. It's the same basic idea as before, but the filter could actually be used to execute the action via reflection. I don't think I would recommend this route, but in this contrived example, you can see how it might be done:

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 );
    }
}

This could then be used like this:

[TimeoutFilter( 10000 )]
public string Get( )
{
    Thread.Sleep( 5000 );
    return "Results";
}

This works for simple types (e.g. string), giving us: <z:anyType i:type="d1p1:string">Results</z:anyType> in Firefox, though as you can see, the serialization is not ideal. Using custom types with this exact code will be a bit problematic as far as serialization goes, but with some work, this could probably be useful in some specific scenarios. That the action parameters come in the form of a dictionary instead of an array could also pose some issues in terms of the parameter ordering. Obviously having real support for this would be better.

As far as the vNext stuff goes, they may well be planning to add the ability to do server-side timeouts for Web API since MVC and API controllers are being unified. If they do, it will likely not be through the System.Web.Mvc.AsyncTimeoutAttribute class, as they are explicitly removing dependencies on System.Web.

As of today, it doesn't appear that adding a System.Web.Mvc entry to the project.json file works, but this may well change. If it does, while you wouldn't be able to use the new cloud-optimized framework with such code, you might be able to use the AsyncTimeout attribute on code that is only intended to run with the full .NET framework.

For what it's worth, this is what I tried adding to project.json. Perhaps a specific version would have made it happier?

"frameworks": {
    "net451": {
        "dependencies": { 
            "System.Web.Mvc": ""
        }
    }
}

A reference to it does show up in the Solution Explorer's references list, but it does so with a yellow exclamation point indicating a problem. The application itself returns 500 errors while this reference remains.

这篇关于Web Api请求超时?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆