HttpClient SendAsync bloqueia o thread principal
Eu escrevi um pequeno aplicativo winforms que envia solicitações de http para cada endereço IP em minha rede local para descobrir um determinado dispositivo meu. Na minha máscara de sub-rede específica, são 512 endereços. Eu escrevi isso usando backGroundWorker, mas queria experimentar httpClient e o padrão Async / Await para conseguir a mesma coisa. O código abaixo usa uma única instância de httpClient e espero até que todas as solicitações sejam concluídas. Esse problema é que o thread principal fica bloqueado. Eu sei disso porque tenho uma caixa de imagens + gif de carregamento e não está animando uniformemente. Coloquei o método GetAsync em um Task.Run conforme sugerido aqui, mas também não funcionou.
private async void button1_Click(object sender, EventArgs e)
{
var addresses = networkUtils.generateIPRange..
await MakeMultipleHttpRequests(addresses);
}
public async Task MakeMultipleHttpRequests(IPAddress[] addresses)
{
List<Task<HttpResponseMessage>> httpTasks = new List<Task<HttpResponseMessage>>();
foreach (var address in addresses)
{
Task<HttpResponseMessage> response = MakeHttpGetRequest(address.ToString());
httpTasks.Add(response);
}
try
{
if (httpTasks.ToArray().Length != 0)
{
await Task.WhenAll(httpTasks.ToArray());
}
}
catch (Exception ex)
{
Console.WriteLine("\thttp tasks did not complete Exception : {0}", ex.Message);
}
}
private async Task<HttpResponseMessage> MakeHttpGetRequest(string address)
{
var url = string.Format("http://{0}/getStatus", address);
var cts = new System.Threading.CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(10));
HttpResponseMessage response = null;
var request = new HttpRequestMessage(HttpMethod.Get, url);
response = await httpClient.SendAsync(request, cts.Token);
return response;
}
Eu li um problema semelhante aqui, mas meu thread gui não está fazendo muito. Eu li aqui que talvez esteja ficando sem tópicos. É esse o problema, como posso resolvê-lo? Eu sei que é o Send Async porque se eu substituir o código pela tarefa simples abaixo não há bloqueio.
await Task.Run(() =>
{
Thread.Sleep(1000);
});
Respostas
Então, um dos problemas aqui é que você está criando mais de 500 tarefas uma após a outra em rápida sucessão com um tempo limite definido fora da criação da tarefa.
Só porque você pediu para executar mais de 500 tarefas, não significa que mais de 500 tarefas serão executadas ao mesmo tempo. Eles são colocados na fila e executados quando o planejador considera que é possível.
Você define um tempo limite de 10 segundos no momento da criação. Mas eles podem permanecer no planejador por 10 segundos antes mesmo de serem executados.
Você deseja que suas solicitações Http atinjam o tempo limite organicamente, você pode fazer isso assim ao criar HttpClient:
private static readonly HttpClient _httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(10)
};
Portanto, ao mover o tempo limite para o HttpClient, seu método deve ficar assim:
private static Task<HttpResponseMessage> MakeHttpGetRequest(string address)
{
return _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, new UriBuilder
{
Host = address,
Path = "getStatus"
}.Uri));
}
Tente usar esse método e veja se melhora o problema de travamento no modo de depuração.
Quanto ao problema que você estava tendo: ele está travando porque você está no modo de depuração e o depurador está tentando dizer "ei, você obteve uma exceção" 500 vezes, tudo ao mesmo tempo, porque todas foram geradas ao mesmo tempo . Execute-o no modo Release e veja se ele ainda trava.
O que eu consideraria fazer é distribuir em lote suas operações. Faça 20, depois espere até que essas 20 terminem, faça mais 20, e assim por diante.
Se você gostaria de ver uma maneira inteligente de agrupar tarefas, me avise e eu ficaria mais do que feliz em mostrar a você.
No .NET Framework, o número de conexões com um servidor é controlado pela classe ServicePointManager .
Para um cliente, o limite de conexão padrão é 2 em processos do cliente.
Não importa quantas invocações HttpClient.SendAsync você faça, apenas 2 estarão ativas ao mesmo tempo.
Mas você mesmo pode gerenciar as conexões .
No .NET Core, aqui não existe o conceito de gerenciador de ponto de serviço e o limite padrão equivalente é int.MaxValue.