HttpClient SendAsync blocca il thread principale

Aug 21 2020

Ho scritto una piccola applicazione winforms che invia richieste http a ogni indirizzo IP all'interno della mia rete locale per scoprire un certo mio dispositivo. Sulla mia particolare maschera di sottorete sono 512 indirizzi. L'ho scritto usando backGroundWorker ma volevo provare httpClient e il pattern Async / Await per ottenere la stessa cosa. Il codice seguente utilizza una singola istanza di httpClient e aspetto il completamento di tutte le richieste. Questo problema è che il thread principale viene bloccato. Lo so perché ho una picturebox + caricamento gif e non si anima in modo uniforme. Ho inserito il metodo GetAsync in un Task.Run come suggerito qui, ma neanche quello ha funzionato.

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

Ho letto un problema simile qui ma il mio thread della GUI non sta facendo molto. Ho letto qui che forse sto esaurendo i thread. È questo il problema, come posso risolverlo? So che è Invia asincrono perché se sostituisco il codice con la semplice attività di seguito non ci sono blocchi.

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

Risposte

2 Andy Aug 21 2020 at 05:31

Quindi uno dei problemi qui è che stai creando più di 500 attività una dopo l'altra in rapida successione con un timeout impostato al di fuori della creazione dell'attività.

Solo perché chiedi di eseguire più di 500 attività, non significa che più di 500 attività verranno eseguite tutte allo stesso tempo. Vengono messi in coda ed eseguiti quando lo scheduler lo ritiene possibile.

Hai impostato un timeout al momento della creazione di 10 secondi. Ma potrebbero rimanere seduti nello scheduler per 10 secondi prima ancora di essere eseguiti.

Vuoi che le tue richieste Http scadano organicamente, puoi farlo in questo modo quando crei HttpClient:

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

Quindi, spostando il timeout su HttpClient, il tuo metodo dovrebbe ora assomigliare a questo:

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

Prova a utilizzare quel metodo e verifica se migliora il tuo problema di blocco in modalità di debug.

Per quanto riguarda il problema che stavi riscontrando: si sta bloccando perché sei in modalità di debug e il debugger sta cercando di dire "ehi, hai ricevuto un'eccezione" 500 volte tutte contemporaneamente perché sono state generate tutte allo stesso tempo . Eseguilo in modalità di rilascio e verifica se si blocca ancora.

Quello che considererei di fare è distribuire le tue operazioni. Fai 20, poi aspetta che finiscano quei 20, fai altri 20, così via e così via.

Se desideri vedere un modo intelligente di raggruppare le attività, fammelo sapere e sarei più che felice di mostrartelo.

1 PauloMorgado Aug 21 2020 at 17:03

In .NET Framework, il numero di connessioni a un server è controllato dalla classe ServicePointManager .

Per un client, il limite di connessione predefinito è 2 sui processi client.

Indipendentemente dal numero di invocazioni HttpClient.SendAsync eseguite, solo 2 saranno attive contemporaneamente.

Ma puoi gestire le connessioni da solo.

In .NET Core qui non è il concetto di Service Point Manager e il limite predefinito equivalente è int.MaxValue.