簡體   English   中英

超時 Web Api 請求?

[英]Timeout a Web Api request?

就像 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依賴項。

截至今天,將System.Web.Mvc條目添加到project.json文件似乎不起作用,但這很可能會改變。 如果是這樣,雖然您將無法將新的雲優化框架與此類代碼一起使用,但您可能可以在僅打算與完整 .NET 框架一起運行的代碼上使用AsyncTimeout屬性。

對於它的價值,這就是我嘗試添加到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();
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM