Я прочитал эту ошибку «Поток уже использован», используя Polly для повторения запросов в ASP.NET Core, но не вижу, где клонировать сообщение HTTP-запроса.
Итак, в моем коде я регистрирую httpclient следующим образом:
var httpClientBuilder = serviceCollection.AddHttpClient("CustomHttpClientName");
httpClientBuilder.AddHttpMessageHandler(ctx=>...)
httpClientBuilder.AddPolicyHandler(GetRetryPolicy());
private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return HttpPolicyExtensions.HandleTransientHttpError()
.OrResult(result => !result.IsSuccessStatusCode)
.OrResult(result =>
{
var elem = result.Content.ReadFromJsonAsync<JsonElement>().GetAwaiter().GetResult();
return elem.TryGetProperty("error", out _);
})
.WaitAndRetryAsync(
sleepDurations: Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromSeconds(1), retryCount: 3),
onRetry: (outcome, timespan, retryCount, context) =>
{
context.GetLogger()?.LogWarning("Delaying for {Delay}ms, then making retry {Retry}.", timespan.TotalMilliseconds, retryCount);
});
}
а затем вызов HTTP-клиента:
var request = CreateRequest(jsonContent, HttpMethod.Post, uri);
await GetClient().SendAsync(request, cancellationToken); // here I get runtime exception
Я хочу повторить попытку, если в ответном json вижу поле error
.
Приведенный выше код приводит к исключению InvalidOperationException: «Поток уже использован. Его нельзя прочитать еще раз»
Вы не можете повторно использовать HttpRequestMessage
, потому что существует проверка повторного использования . Вам следует заново создавать объект запроса для каждой повторной попытки.
@PeterCsala: проблема не в повторных попытках. Когда запрос будет успешным, код-потребитель захочет прочитать результат, но код проверки ошибок в этой политике уже прочитает из потока.
Думайте о ручье как о «спуске» - большинство ручьев не могут снова подняться на холм, чтобы снова спуститься без каких-либо дополнительных усилий - как иногда делает насос на реальном ручье - но тогда действительно ли это тот же самый ручей или новый? поток с одинаковым/похожим содержимым
@StriplingWarrior Извините, я неправильно понял, вы правы.
Я не проверял это, но, возможно, удастся сохранить исходный результат в памяти, пока вы его читаете, вот так:
.OrResult(result =>
{
if (result.Content is not ByteArrayContent)
{
var originalContent = result.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult();
result.Content = new ByteArrayContent(originalContent);
}
var elem = result.Content.ReadFromJsonAsync<JsonElement>().GetAwaiter().GetResult();
return elem.TryGetProperty("error", out _);
})
Заменив содержимое копией в памяти, вы разрешите его многократное чтение. Просто имейте в виду, что это меняет некоторые поведения вашего приложения: код-потребитель не будет «завершать» запрос до тех пор, пока не будет прочитан весь ответ. Но если вы ожидаете прочитать ответ в формате JSON, это, вероятно, не имеет большого значения.
Другим возможным способом решения этой проблемы было бы разделение обязанностей. IAsyncPolicy<HttpResponseMessage>
может фокусироваться только на самом HTTP-ответе (не обращая внимания на контент). Тогда у вас может быть отдельный служебный класс для получения конкретного типа ответа JSON, который вы ищете, и использовать IAsyncPolicy<JsonElement>
для комбинированных действий по получению ответа и чтению из него JSON. Тогда ваш критерий «дескриптора» будет напрямую смотреть на JsonElement
, а не пытаться напрямую прочитать JSON из потока контента.
Также обратите внимание, что Polly.Extensions.Http устарел в пользу Microsoft.Extensions.Http.Resilience.
Чем это отличается от Content LoadIntoBufferAsync
?
@PeterCsala: Отличный призыв. Я не знал об этом методе. Похоже, это сделает то же самое.
Хотя, если честно, я все меньше убеждаюсь в том, что правильно понял первопричину. В ходе простого тестирования мне не удалось воспроизвести сообщенное исключение.
Связано: github.com/App-vNext/Polly/issues/854 ?