MVC WebApiが非同期ASP.NETパイプラインで実行されるように、 実行タイムアウトはサポートされていません を意味します。
MVCでは、[AsyncTimeout]
フィルタ、WebApiにはこれがありません。では、WebApiでリクエストをタイムアウトするにはどうすればよいですか?
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エラーを返します。
WebAPIでは、通常、サーバー側ではなくclient側でタイムアウトを処理します。これは、 I quote :
HTTPリクエストをキャンセルする方法は、HttpClientで直接リクエストをキャンセルすることです。その理由は、複数のリクエストが単一のHttpClient内でTCP接続を再利用できるため、他のリクエストにも影響を与えずに単一のリクエストを安全にキャンセルできないためです。
リクエストのタイムアウトを制御できます。正しくリコールした場合、HttpClientHandlerにあると思います。
API側にタイムアウトを実際に実装する必要がある場合は、作業を行うスレッドを作成し、一定期間後にキャンセルすることをお勧めします。たとえば、それをTask
に入れ、Task.Wait
を使用して 'timeout'タスクを作成し、最初に戻るためにTask.WaitAny
を使用できます。これにより、タイムアウトをシミュレートできます。
同様に、特定の操作を実行している場合は、既にタイムアウトがサポートされているかどうかを確認してください。かなり頻繁に、WebAPIからHttpWebRequest
を実行し、その Timeout プロパティを指定します。
基本コントローラーに次のメソッドを追加して、生活を楽にします。
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();
}