web-dev-qa-db-ja.com

Web APIリクエストをタイムアウトしますか?

MVC WebApiが非同期ASP.NETパイプラインで実行されるように、 実行タイムアウトはサポートされていません を意味します。

MVCでは、[AsyncTimeout]フィルタ、WebApiにはこれがありません。では、WebApiでリクエストをタイムアウトするにはどうすればよいですか?

15
DalSoft

Mendhakの提案に基づいて、かなりの数のフープをジャンプすることなく、希望どおりに実行することはできませんが、希望どおりに実行することは可能です。 withoutを実行すると、フィルターは次のようになります。

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

ここでは、実際の「作業」がタイムアウトよりも長くかかるため、タイムアウトが表示されます。

あなたが望むことをするためにwith属性は可能ですが、理想的ではありません。以前と同じ基本的な考え方ですが、実際にはフィルターを使用して、リフレクションを介してアクションを実行できます。私はこのルートをお勧めするとは思いませんが、この不自然な例では、それがどのように行われるかを見ることができます:

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エラーを返します。

17
Adam

WebAPIでは、通常、サーバー側ではなくclient側でタイムアウトを処理します。これは、 I quote

HTTPリクエストをキャンセルする方法は、HttpClientで直接リクエストをキャンセルすることです。その理由は、複数のリクエストが単一のHttpClient内でTCP接続を再利用できるため、他のリクエストにも影響を与えずに単一のリクエストを安全にキャンセルできないためです。

リクエストのタイムアウトを制御できます。正しくリコールした場合、HttpClientHandlerにあると思います。

API側にタイムアウトを実際に実装する必要がある場合は、作業を行うスレッドを作成し、一定期間後にキャンセルすることをお勧めします。たとえば、それをTaskに入れ、Task.Waitを使用して 'timeout'タスクを作成し、最初に戻るためにTask.WaitAnyを使用できます。これにより、タイムアウトをシミュレートできます。

同様に、特定の操作を実行している場合は、既にタイムアウトがサポートされているかどうかを確認してください。かなり頻繁に、WebAPIからHttpWebRequestを実行し、その Timeout プロパティを指定します。

10
Mendhak

基本コントローラーに次のメソッドを追加して、生活を楽にします。

    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();
    }
0
Daniel Dantas