Тайм-аут запроса веб-Api?
Как и MVC WebApi работает на асинхронном конвейере ASP.NET, что означает время ожидания выполнения не поддерживается.
В MVC я использую фильтр [AsyncTimeout]
, у WebApi этого нет. Итак, как мне пропустить запрос в WebApi?
Ответы
Ответ 1
Итак, для ASP.NET Core он выглядит как эквивалент AsyncTimeOutAttribute не будет реализован.
Все не потеряно, хотя в ASP.NET Core WebApi и MVC были объединены в один проект MVC, и в отличие от предыдущих версий (по крайней мере, при размещении с использованием IIS) Тайм-аут запроса поддерживается через web.config.
Значение по умолчанию - 2 минуты
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
</handlers>
<aspNetCore
requestTimeout="00:02:00"
processPath="%LAUNCHER_PATH%"
arguments="%LAUNCHER_ARGS%"
stdoutLogEnabled="false"
stdoutLogFile=".\logs\stdout" />
</system.webServer>
</configuration>
Если у вас нет синхронной блокировки кода, тайм-ауты запросов не являются проблемой в ASP.NET Core. Возвращение асинхронной задачи в ASP.NET Core возвращает поток обратно в пул потоков, чтобы можно было обрабатывать другие запросы. Дополнительная информация о том, как Kestrel работает с IIS в качестве резервного прокси.
Ответ 2
Основываясь на предложении Мендхака, можно делать то, что вы хотите, хотя и не совсем так, как вы хотели бы сделать это, не пробираясь через несколько обручей. Выполнение без фильтра может выглядеть примерно так:
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";
}
Это работает для простых типов (например, string), давая нам: <z:anyType i:type="d1p1:string">Results</z:anyType>
в Firefox, хотя, как вы можете видеть, сериализация не идеальна. Использование пользовательских типов с этим точным кодом будет немного проблематичным, поскольку сериализация идет, но с некоторой работой это может быть полезно в некоторых конкретных сценариях. То, что параметры действия поступают в виде словаря, а не массива, также может представлять некоторые проблемы с точки зрения упорядочения параметров. Очевидно, что иметь реальную поддержку для этого было бы лучше.
Что касается материалов vNext, они вполне могут планировать добавить возможность выполнять тайм-ауты на стороне сервера для веб-API, поскольку контроллеры MVC и API объединяются. Если они это сделают, это, скорее всего, не будет проходить через класс System.Web.Mvc.AsyncTimeoutAttribute
, поскольку они явно удаляют зависимости от System.Web
.
На сегодняшний день не кажется, что добавление записи System.Web.Mvc
в файл project.json
работает, но это может измениться. Если это произойдет, хотя вы не сможете использовать новую оптимизированную для облаков инфраструктуру с таким кодом, вы можете использовать атрибут AsyncTimeout
для кода, который предназначен только для работы с полной платформой .NET.
Для чего это стоит, я попытался добавить к project.json
. Возможно, конкретная версия сделала бы ее более счастливой?
"frameworks": {
"net451": {
"dependencies": {
"System.Web.Mvc": ""
}
}
}
Ссылка на него отображается в списке рекомендаций Solution Explorer, но она делает это с желтым восклицательным знаком, указывающим на проблему. Само приложение возвращает 500 ошибок, пока эта ссылка остается.
Ответ 3
В WebAPI вы обычно обрабатываете тайм-ауты на стороне клиента, а не на стороне сервера. Это потому, что и цитирую:
Способ отмены HTTP-запросов заключается в немедленном их удалении по HttpClient. Причина заключается в том, что несколько запросов могут повторно использовать TCP-соединения в одном HttpClient, и поэтому вы не можете безопасно отменить один запрос, не влияя на другие запросы.
Вы можете управлять таймаутом для запросов - я думаю, что это на HttpClientHandler, если я правильно помню.
Если вам действительно нужно реализовать тайм-аут самой стороны API, я бы рекомендовал создать поток для выполнения вашей работы, а затем отменить его через определенный период. Например, вы можете положить его в Task
, создать свою задачу "timeout" с помощью Task.Wait
и использовать Task.WaitAny
для первой, чтобы вернуться. Это может имитировать тайм-аут.
Аналогично, если вы выполняете определенную операцию, проверьте, поддерживает ли она время ожидания. Довольно часто я выполняю HttpWebRequest
из своего WebAPI и указываю его свойство Timeout.
Ответ 4
Сделайте свою жизнь проще, в вашем базовом контроллере добавьте следующий метод:
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();
}