HttpClient SendAsync bloquea el hilo principal

Aug 21 2020

He escrito una pequeña aplicación winforms que envía solicitudes http a cada dirección IP dentro de mi red local para descubrir un determinado dispositivo mío. En mi máscara de subred particular, eso es 512 direcciones. Escribí esto usando backGroundWorker pero quería probar httpClient y el patrón Async / Await para lograr lo mismo. El siguiente código usa una sola instancia de httpClient y espero hasta que se hayan completado todas las solicitudes. Este problema es que el hilo principal se bloquea. Lo sé porque tengo un cuadro de imágenes + gif cargando y no se anima de manera uniforme. Puse el método GetAsync en Task.Run como se sugiere aquí, pero eso tampoco funcionó.

    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;
    }

He leído un problema similar aquí, pero mi hilo de interfaz gráfica de usuario no está haciendo mucho. He leído aquí que tal vez me estoy quedando sin hilos. ¿Es este el problema, cómo puedo resolverlo? Sé que es Send Async porque si reemplazo el código con la tarea simple a continuación, no hay bloqueo.

    await Task.Run(() =>
    {
       Thread.Sleep(1000);
    });

Respuestas

2 Andy Aug 21 2020 at 05:31

Entonces, uno de los problemas aquí es que está creando más de 500 tareas una tras otra en rápida sucesión con un tiempo de espera establecido fuera de la creación de la tarea.

El hecho de que solicite ejecutar más de 500 tareas no significa que más de 500 tareas se ejecutarán al mismo tiempo. Se ponen en cola y se ejecutan cuando el programador lo considera posible.

Establece un tiempo de espera en el momento de la creación de 10 segundos. Pero podrían sentarse en el programador durante 10 segundos antes de que se ejecuten.

Desea que sus solicitudes Http se agoten orgánicamente, puede hacerlo así cuando cree HttpClient:

private static readonly HttpClient _httpClient = new HttpClient
{
    Timeout = TimeSpan.FromSeconds(10)
};

Entonces, al mover el tiempo de espera al HttpClient, su método ahora debería verse así:

private static Task<HttpResponseMessage> MakeHttpGetRequest(string address)
{
    return _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, new UriBuilder
    {
        Host = address,
        Path = "getStatus"
    }.Uri));
}

Intente usar ese método y vea si mejora su problema de bloqueo en el modo de depuración.

Por lo que el problema que estaba teniendo: Se encerrando porque usted está en el modo de depuración y el depurador está tratando de decir "oye, tienes una excepción" 500 veces todos al mismo tiempo , porque todos fueron generaron al mismo tiempo . Ejecútelo en modo Release y vea si todavía se bloquea.

Lo que consideraría hacer es agrupar sus operaciones. Haz 20, luego espera hasta que terminen esos 20, haz 20 más, y así sucesivamente.

Si desea ver una forma ingeniosa de agrupar las tareas, hágamelo saber y estaré más que feliz de mostrárselo.

1 PauloMorgado Aug 21 2020 at 17:03

En .NET Framework, la clase ServicePointManager controla el número de conexiones a un servidor .

Para un cliente, el límite de conexión predeterminado es 2 en los procesos del cliente.

No importa cuántas invocaciones de HttpClient.SendAsync realice, solo 2 estarán activas al mismo tiempo.

Pero puede administrar las conexiones usted mismo.

En .NET Core, aquí no es el concepto de administrador de puntos de servicio y el límite predeterminado equivalente es int.MaxValue.