Por que o cancelamento bloqueia por tanto tempo ao cancelar muitas solicitações HTTP?
Eu tenho um código que executa o processamento de páginas HTML em lote usando o conteúdo de um host específico. Ele tenta fazer um grande número (~ 400) de solicitações HTTP simultâneas usandoHttpClient
. Eu acredito que o número máximo de conexões simultâneas é restrito porServicePointManager.DefaultConnectionLimit
, portanto, não estou aplicando minhas próprias restrições de concorrência.
Depois de enviar todos os pedidos de forma assíncrona paraHttpClient
usandoTask.WhenAll
, toda a operação em lote pode ser cancelada usandoCancellationTokenSource
eCancellationToken
. O progresso da operação é visível através de uma interface de usuário, e um botão pode ser clicado para realizar o cancelamento.
A chamada paraCancellationTokenSource.Cancel()
blocos por aproximadamente 5 a 30 segundos. Isso faz com que a interface do usuário congele. É suspeito que isso ocorra porque o método está chamando o código registrado para notificação de cancelamento.
HttpClient
já parece colocar em fila as solicitações em excesso.Executando oCancellationTokenSource.Cancel()
chamada de método em um thread não-UI. Isso não funcionou muito bem; a tarefa não foi executada até que a maioria das outras terminasse. Acho que umasync
A versão do método funcionaria bem, mas não consegui encontrar uma. Além disso, tenho a impressão de que é adequado usar o método em um thread de interface do usuário.DemonstraçãoCódigoclass Program
{
private const int desiredNumberOfConnections = 418;
static void Main(string[] args)
{
ManyHttpRequestsTest().Wait();
Console.WriteLine("Finished.");
Console.ReadKey();
}
private static async Task ManyHttpRequestsTest()
{
using (var client = new HttpClient())
using (var cancellationTokenSource = new CancellationTokenSource())
{
var requestsCompleted = 0;
using (var allRequestsStarted = new CountdownEvent(desiredNumberOfConnections))
{
Action reportRequestStarted = () => allRequestsStarted.Signal();
Action reportRequestCompleted = () => Interlocked.Increment(ref requestsCompleted);
Func<int, Task> getHttpResponse = index => GetHttpResponse(client, cancellationTokenSource.Token, reportRequestStarted, reportRequestCompleted);
var httpRequestTasks = Enumerable.Range(0, desiredNumberOfConnections).Select(getHttpResponse);
Console.WriteLine("HTTP requests batch being initiated");
var httpRequestsTask = Task.WhenAll(httpRequestTasks);
Console.WriteLine("Starting {0} requests (simultaneous connection limit of {1})", desiredNumberOfConnections, ServicePointManager.DefaultConnectionLimit);
allRequestsStarted.Wait();
Cancel(cancellationTokenSource);
await WaitForRequestsToFinish(httpRequestsTask);
}
Console.WriteLine("{0} HTTP requests were completed", requestsCompleted);
}
}
private static void Cancel(CancellationTokenSource cancellationTokenSource)
{
Console.Write("Cancelling...");
var stopwatch = Stopwatch.StartNew();
cancellationTokenSource.Cancel();
stopwatch.Stop();
Console.WriteLine("took {0} seconds", stopwatch.Elapsed.TotalSeconds);
}
private static async Task WaitForRequestsToFinish(Task httpRequestsTask)
{
Console.WriteLine("Waiting for HTTP requests to finish");
try
{
await httpRequestsTask;
}
catch (OperationCanceledException)
{
Console.WriteLine("HTTP requests were cancelled");
}
}
private static async Task GetHttpResponse(HttpClient client, CancellationToken cancellationToken, Action reportStarted, Action reportFinished)
{
var getResponse = client.GetAsync("http://www.google.com", cancellationToken);
reportStarted();
using (var response = await getResponse)
response.EnsureSuccessStatusCode();
reportFinished();
}
}
SaídaPor que o bloqueio de cancelamento por tanto tempo? Além disso, há alguma coisa que eu esteja fazendo errado ou poderia estar fazendo melhor?