HttpClient вызывает TaskCanceledException
Код ниже иногда(!) вызывает TaskCanceledException. Из-за чего такое происходит и как этого избежать? И если избежать не выйдет, то единственный путь - в catch повторно отправлять запрос?
var client = _clientFactory.CreateClient();
// Пробовал поставить client.Timeout = TimeSpan.FromMinutes(30);
// Однако поток просто был залочен на полчаса, а потом вернул всё
// тот же TaskCanceledException
HttpResponseMessage response = null;
try
{
// request заранее определён. HttpMetod Post
response = await client.SendAsync(request: request);
}
catch
{
throw new NotImplementedException();
}
UPD: Текст исключения
System.Threading.Tasks.TaskCanceledException: The operation was canceled.
---> System.IO.IOException: Unable to read data from the transport connection: Операция ввода/вывода была прервана из-за завершения потока команд или по запросу приложения..
---> System.Net.Sockets.SocketException (995): Операция ввода/вывода была прервана из-за завершения потока команд или по запросу приложения.
--- End of inner exception stack trace ---
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.GetResult(Int16 token)
at System.Net.Security.SslStream.g__InternalFillBufferAsync|215_0[TReadAdapter](TReadAdapter adap, ValueTask1 task, Int32 min, Int32 initial)1 buffer)
at System.Net.Security.SslStream.ReadAsyncInternal[TReadAdapter](TReadAdapter adapter, Memory
at System.Net.Http.HttpConnection.FillAsync()
at System.Net.Http.HttpConnection.CopyToContentLengthAsync(Stream destination, UInt64 length, Int32 bufferSize, CancellationToken cancellationToken)
at System.Net.Http.HttpConnection.ContentLengthReadStream.CompleteCopyToAsync(Task copyTask, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Net.Http.HttpConnection.ContentLengthReadStream.CompleteCopyToAsync(Task copyTask, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionResponseContent.SerializeToStreamAsync(Stream stream, TransportContext context, CancellationToken cancellationToken)
at System.Net.Http.HttpContent.LoadIntoBufferAsyncCore(Task serializeToStreamTask, MemoryStream tempBuffer)
at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
at Post_v9.Classes.EBayAPI.SellAPI.FulfillmentAPI.EBayAPI_GetterOrders.GetOrders(IHttpClientFactory _clientFactory, String accessToken) in MyCode.cs:line 44
Ответы (1 шт):
HttpClient при таймауте бросает TaskCanceledException, а не TimeoutException или HttpRequestException.
Видимо авторы считают верной метафору "операция запроса была ведь отменена (механизмом отмены по таймауту), значит TaskCanceledException". А может просто забыли обработать этот случай и обернуть исключение.
А потому приходится ловить это исключение при запросе и сверять CancellationToken.
Лично я считаю, что правильно кидать TimeoutHttpRequestException (наследник от HttpRequestException), как метафора "запрос не был успешен" и его можно перехватить и повторить, но если не нужно знать причину сбоя запроса, то не нужно писать лишний catch (как в случае с TimeoutException)
Как решал я
- Делал метод расширение SendAsyncEx, где ловил исключение и кидал вместо него нужное мне (HttpRequestException).
- Пытался сделать наследника HttpClient, где перегрузить метод SendAsync, но безуспешно
- Сделать свой DelegatingHandler, где подменять исключение. Работает, но это неудобный костыль.
- Написал обертку над HttpClient - ей и пользуюсь. Но ее нельзя передать туда, где ждут родной HttpClient и про нее не знает IHttpClientFactory.