HttpClient SendAsync bloque le thread principal

Aug 21 2020

J'ai écrit une petite application winforms qui envoie des requêtes http à chaque adresse IP de mon réseau local pour découvrir un de mes appareils. Sur mon masque de sous-réseau particulier, c'est 512 adresses. J'ai écrit ceci en utilisant backGroundWorker mais je voulais essayer httpClient et le modèle Async / Await pour obtenir la même chose. Le code ci-dessous utilise une seule instance de httpClient et j'attends que toutes les demandes soient terminées. Ce problème est que le thread principal est bloqué. Je le sais parce que j'ai une imagebox + chargement de gif et son animation n'est pas uniforme. J'ai mis la méthode GetAsync dans un Task.Run comme suggéré ici mais cela n'a pas fonctionné non plus.

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

J'ai lu un problème similaire ici mais mon fil d'interface graphique ne fait pas grand-chose. J'ai lu ici que je manquais peut-être de fils. Est-ce le problème, comment puis-je le résoudre? Je sais que c'est Send Async car si je remplace le code par la tâche simple ci-dessous, il n'y a pas de blocage.

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

Réponses

2 Andy Aug 21 2020 at 05:31

L'un des problèmes ici est que vous créez plus de 500 tâches l'une après l'autre en succession rapide avec un délai d'expiration défini en dehors de la création de la tâche.

Ce n'est pas parce que vous demandez d'exécuter plus de 500 tâches que 500+ tâches vont toutes s'exécuter en même temps. Ils sont mis en file d'attente et s'exécutent lorsque le planificateur le juge possible.

Vous définissez un délai d'expiration au moment de la création de 10 secondes. Mais ils pourraient rester dans le planificateur pendant 10 secondes avant même d'être exécutés.

Vous voulez que vos requêtes Http expirent de manière organique, vous pouvez le faire comme ceci lorsque vous créez le HttpClient:

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

Ainsi, en déplaçant le délai d'expiration vers le HttpClient, votre méthode devrait maintenant ressembler à ceci:

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

Essayez d'utiliser cette méthode et voyez si cela améliore votre problème de verrouillage en mode débogage.

En ce qui concerne la question que vous étiez HAVING: Il est verrouillait parce que vous êtes en mode débogage et le débogueur essaie de dire « hey, vous avez obtenu une exception » 500 fois tous en même temps , car ils ont tous été donné naissance en même temps . Exécutez-le en mode Release et voyez s'il se bloque toujours.

Ce que j'envisagerais de faire, c'est de regrouper vos opérations. Faites 20, puis attendez la fin de ces 20, faites 20 de plus, ainsi de suite.

Si vous souhaitez voir une manière astucieuse de regrouper les tâches, faites-le moi savoir et je serais plus qu'heureux de vous le montrer.

1 PauloMorgado Aug 21 2020 at 17:03

Sur .NET Framework, le nombre de connexions à un serveur est contrôlé par la classe ServicePointManager .

Pour un client, la limite de connexion par défaut est de 2 sur les processus clients.

Quel que soit le nombre d'appels HttpClient.SendAsync que vous faites, seuls 2 seront actifs en même temps.

Mais vous pouvez gérer vous-même les connexions .

Sur .NET Core, ce n'est pas le concept de gestionnaire de points de service et la limite par défaut équivalente est int.MaxValue.