web-dev-qa-db-fra.com

HttpClient et HttpClientHandler doivent-ils être supprimés?

System.Net.Http.HttpClient et System.Net.Http.HttpClientHandler dans .NET Framework 4.5 implémentent IDisposable (via System.Net.Http.HttpMessageInvoker ).

La documentation de l'instruction using indique:

En règle générale, lorsque vous utilisez un objet IDisposable, vous devez le déclarer et l'instancier dans une instruction using.

Cette réponse utilise ce modèle:

var baseAddress = new Uri("http://example.com");
var cookieContainer = new CookieContainer();
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
using (var client = new HttpClient(handler) { BaseAddress = baseAddress })
{
    var content = new FormUrlEncodedContent(new[]
    {
        new KeyValuePair<string, string>("foo", "bar"),
        new KeyValuePair<string, string>("baz", "bazinga"),
    });
    cookieContainer.Add(baseAddress, new Cookie("CookieName", "cookie_value"));
    var result = client.PostAsync("/test", content).Result;
    result.EnsureSuccessStatusCode();
}

Mais les exemples les plus visibles de Microsoft n'appellent pas Dispose() ni explicitement ni implicitement. Par exemple:

Dans les commentaires de l'annonce , une personne a demandé à l'employé de Microsoft:

Après avoir vérifié vos échantillons, j'ai constaté que vous n'aviez pas effectué l'action de suppression sur l'instance HttpClient. J'ai utilisé toutes les instances de HttpClient avec une instruction using sur mon application et je pensais que c'était la bonne façon, car HttpClient implémente l'interface IDisposable. Suis-je sur le bon chemin?

Sa réponse fut:

En général, c’est correct même si vous devez faire attention à "using" et à l’async, car ils ne se mélangent pas vraiment en .Net 4. En .Net 4.5, vous pouvez utiliser "wait" dans une instruction "using".

Au fait, vous pouvez réutiliser le même HttpClient autant de fois que vous le souhaitez, de sorte que vous ne les créez pas et ne les supprimez pas tout le temps.

Le deuxième paragraphe est superflu pour cette question, qui ne se soucie pas du nombre de fois où vous pouvez utiliser une instance de HttpClient, mais de la nécessité de la disposer après l’avoir perdu.

(Mise à jour: en fait, ce deuxième paragraphe est la clé de la réponse, comme indiqué ci-dessous par @DPeden.)

Donc mes questions sont:

  1. Est-il nécessaire, étant donné l'implémentation actuelle (.NET Framework 4.5), d'appeler Dispose () sur les instances HttpClient et HttpClientHandler? Clarification: par "nécessaire", je veux dire s'il y a des conséquences négatives pour ne pas disposer, telles que des fuites de ressources ou des risques de corruption de données.

  2. Si ce n'est pas nécessaire, s'agirait-il d'une "bonne pratique" de toute façon, puisqu'ils mettent en œuvre IDisposable?

  3. Si cela est nécessaire (ou recommandé), est-ce que ce code mentionné ci-dessus est implémenté en toute sécurité (pour .NET Framework 4.5)?

  4. Si ces classes ne nécessitent pas l'appel de Dispose (), pourquoi ont-elles été implémentées en tant qu'IDisposable?

  5. S'ils ont besoin, ou si c'est une pratique recommandée, les exemples de Microsoft sont-ils trompeurs ou dangereux?

299
Fernando Correia

Le consensus général est que vous n’avez pas besoin de disposer de HttpClient.

Beaucoup de gens qui sont intimement impliqués dans la façon dont cela fonctionne l'ont déclaré.

Voir article de blog de Darrel Miller et un article lié SO: les résultats de l'analyse HttpClient dans une fuite de mémoire pour référence.

Je vous suggère également fortement de lire le chapitre HttpClient de Conception d’API Web évolutives avec ASP.NET pour connaître le contexte se passe sous le capot, en particulier la section "Lifecycle" citée ici:

Bien que HttpClient implémente indirectement l'interface IDisposable, l'utilisation standard de HttpClient n'est pas de s'en débarrasser après chaque demande. L'objet HttpClient est conçu pour durer aussi longtemps que votre application doit effectuer des requêtes HTTP. La présence d'un objet dans plusieurs demandes permet de définir DefaultRequestHeaders et vous évite de spécifier à nouveau des éléments tels que CredentialCache et CookieContainer sur chaque demande, comme cela était nécessaire avec HttpWebRequest.

Ou même ouvrir DotPeek.

237
David Peden

Les réponses actuelles sont un peu déroutantes et trompeuses et manquent certaines implications importantes pour le DNS. Je vais essayer de résumer la situation.

  1. De manière générale, la plupart des objets IDisposable devraient idéalement être supprimés lorsque vous en avez terminé avec eux , en particulier ceux qui possèdent un système d'exploitation nommé/partagé ressources . HttpClient ne fait pas exception à la règle puisque Darrel Miller indique qu'il alloue des jetons d'annulation et que les corps de requête/réponse peuvent être des flux non gérés.
  2. Toutefois, meilleure pratique pour HttpClient indique que vous devez créer une instance et la réutiliser autant que possible (à l'aide de membres thread-safe dans des scénarios multithreads). Par conséquent, dans la plupart des scénarios , vous n'en disposerez jamais simplement parce que vous en aurez besoin tout le temps .
  3. Le problème avec la réutilisation du même HttpClient "pour toujours" est que la connexion HTTP sous-jacente peut rester ouverte sur l'IP résolue par le DNS d'origine, indépendamment des modifications du DNS . Cela peut poser problème dans des scénarios tels que un déploiement bleu/vert et un basculement basé sur DNS . Il existe différentes approches pour traiter ce problème, la plus fiable impliquant l'envoi par le serveur d'un en-tête Connection:close après les modifications DNS. Une autre possibilité consiste à recycler la HttpClient du côté du client, soit périodiquement, soit via un mécanisme informant du changement DNS. Voir https://github.com/dotnet/corefx/issues/11224 pour plus d'informations (je suggère de le lire attentivement avant d'utiliser aveuglément le code suggéré dans l'article de blog lié).
39
Ohad Schneider

D'après ce que j'ai compris, appeler Dispose() est nécessaire uniquement lorsqu'il s'agit de verrouiller des ressources dont vous aurez besoin ultérieurement (comme une connexion particulière). Il est toujours recommandé de libérer les ressources que vous n'utilisez plus, même si vous n'en avez plus besoin, tout simplement parce que vous ne devriez pas généralement s'accrocher à des ressources que vous n'utilisez pas (jeu de mots voulu).

L'exemple Microsoft n'est pas forcément incorrect. Toutes les ressources utilisées seront libérées lorsque l'application sera fermée. Et dans le cas de cet exemple, cela se produit presque immédiatement après l'utilisation de la HttpClient. Dans les cas similaires, appeler explicitement Dispose() est quelque peu superflu.

Mais, en général, quand une classe implémente IDisposable, il est entendu que vous devez Dispose() de ses instances dès que vous êtes pleinement prêt et capable. Je dirais que cela est particulièrement vrai dans des cas comme HttpClient dans lesquels il n'est pas explicitement documenté de savoir si des ressources ou des connexions sont conservées/ouvertes. Dans le cas où la connexion sera réutilisée à nouveau [bientôt], vous voudrez renoncer à Dipose()ing - vous n'êtes pas "complètement prêt" dans ce cas.

Voir aussi: Méthode IDisposable.Dispose et Quand appeler Dispose

17
svidgen

Dispose () appelle le code ci-dessous, qui ferme les connexions ouvertes par l'instance HttpClient. Le code a été créé par décompilation avec dotPeek.

HttpClientHandler.cs - Éliminer

ServicePointManager.CloseConnectionGroups(this.connectionGroupName);

Si vous n'appelez pas dispose alors ServicePointManager.MaxServicePointIdleTime, qui s'exécute par une minuterie, fermera les connexions http. La valeur par défaut est 100 secondes.

ServicePointManager.cs

internal static readonly TimerThread.Callback s_IdleServicePointTimeoutDelegate = new TimerThread.Callback(ServicePointManager.IdleServicePointTimeoutCallback);
private static volatile TimerThread.Queue s_ServicePointIdlingQueue = TimerThread.GetOrCreateQueue(100000);

private static void IdleServicePointTimeoutCallback(TimerThread.Timer timer, int timeNoticed, object context)
{
  ServicePoint servicePoint = (ServicePoint) context;
  if (Logging.On)
    Logging.PrintInfo(Logging.Web, SR.GetString("net_log_closed_idle", (object) "ServicePoint", (object) servicePoint.GetHashCode()));
  lock (ServicePointManager.s_ServicePointTable)
    ServicePointManager.s_ServicePointTable.Remove((object) servicePoint.LookupString);
  servicePoint.ReleaseAllConnectionGroups();
}

Si vous n'avez pas réglé le temps d'inactivité à l'infini, il semble alors sûr de ne pas appeler de disposition et de laisser la minuterie de connexion en attente entrer en contact et de fermer les connexions pour vous, même s'il serait préférable pour vous d'appeler de disposer d'une instruction using si vous savez que vous avez terminé avec une instance HttpClient et que vous libérez les ressources plus rapidement.

8
Timothy Gonzalez

Réponse courte: Non, l'énoncé de la réponse actuellement acceptée n'est PAS exact : "Le consensus général est que vous n'avez pas (pas besoin) de disposer de HttpClient ".

Réponse longue : LES DEUX affirmations suivantes sont vraies et réalisables en même temps:

  1. "HttpClient est destiné à être instancié une fois et réutilisé tout au long de la vie d'une application", cité par documentation officielle .
  2. Un objet IDisposable est supposé/recommandé pour être éliminé.

Et ils ne sont pas nécessairement en conflit les uns avec les autres. Il s’agit simplement d’organiser votre code pour réutiliser un HttpClient ET le disposer correctement.

Une même réponse plus longue citée dans mon ne autre réponse :

Ce n’est pas une coïncidence de voir des gens dans certains articles de blog blâmer comment l’interface de HttpClient [VARIABLE] _ de IDisposable les incite à utiliser le motif using (var client = new HttpClient()) {...} pour ensuite les amener à problème de gestionnaire de prises épuisé.

Je crois que cela revient à une conception non dite (mal?): "un objet identifiable devrait être de courte durée" .

CEPENDANT, même si cela ressemble certainement à une chose de courte durée lorsque nous écrivons du code dans ce style:

using (var foo = new SomeDisposableObject())
{
    ...
}

le documentation officielle sur IDisposable ne mentionne jamais IDisposable les objets doivent être éphémères. Par définition, IDisposable est simplement un mécanisme vous permettant de libérer des ressources non gérées. Rien de plus. En ce sens, vous êtes censé éventuellement déclencher la cession, mais cela ne vous oblige pas à le faire de manière éphémère.

Il est donc de votre devoir de choisir correctement le moment de déclencher la mise au rebut, en vous basant sur les exigences du cycle de vie de votre objet réel. Rien ne vous empêche d’utiliser un IDisposable de manière durable:

using System;
namespace HelloWorld
{
    class Hello
    {
        static void Main()
        {
            Console.WriteLine("Hello World!");

            using (var client = new HttpClient())
            {
                for (...) { ... }  // A really long loop

                // Or you may even somehow start a daemon here

            }

            // Keep the console window open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }
}

Avec cette nouvelle compréhension, maintenant nous revisitons cet article de blog , nous pouvons clairement remarquer que le "correctif" initialise HttpClient une fois mais ne le dispose jamais, c'est pourquoi nous pouvons voir à partir de sa sortie netstat cela, la connexion reste à l’état ESTABLISHED, ce qui signifie qu’elle n’a PAS été correctement fermée. S'il était fermé, son état serait plutôt dans TIME_WAIT. Dans la pratique, il n’est pas difficile de laisser filtrer une seule connexion ouverte après la fin de votre programme et l’affiche du blog continue d’obtenir un gain de performances après le correctif; mais néanmoins, il est conceptuellement incorrect de blâmer IDisposable et de choisir de NE PAS le jeter.

5
RayLuo

Dans mon cas, je créais un HttpClient dans une méthode qui effectuait réellement l'appel de service. Quelque chose comme:

public void DoServiceCall() {
  var client = new HttpClient();
  await client.PostAsync();
}

Dans un rôle de travailleur Azure, après avoir appelé cette méthode à plusieurs reprises (sans supprimer le HttpClient), elle échouait éventuellement avec SocketException (la tentative de connexion a échoué).

J'ai fait du HttpClient une variable d'instance (la disposant au niveau de la classe) et le problème a disparu. Donc, je dirais, oui, disposez du HttpClient, en supposant que son coffre-fort (vous n'avez pas d'appels asynchrones en attente) pour le faire.

4
David Faivre

En utilisation typique (réponses <2 Go), il n'est pas nécessaire de supprimer les HttpResponseMessages.

Les types de retour des méthodes HttpClient doivent être éliminés si leur contenu de flux n'est pas entièrement lu. Sinon, le CLR ne peut pas savoir que ces flux peuvent être fermés tant qu'ils ne sont pas nettoyés.

  • Si vous lisez les données dans un octet [] (par exemple, GetByteArrayAsync) ou une chaîne, toutes les données sont lues, il n'est donc pas nécessaire de les supprimer.
  • Les autres surcharges liront par défaut le flux jusqu'à 2 Go (HttpCompletionOption est ResponseContentRead, HttpClient.MaxResponseContentBufferSize par défaut est 2 Go).

Si vous définissez HttpCompletionOption sur ResponseHeadersRead ou si la réponse est supérieure à 2 Go, vous devez nettoyer. Cela peut être fait en appelant Dispose sur HttpResponseMessage ou en appelant Dispose/Close sur le flux obtenu à partir du contenu HttpResonseMessage ou en lisant intégralement le contenu.

Que vous appeliez Dispose ou non sur le HttpClient dépend de votre décision d'annuler ou non les demandes en attente.

3
Tom Deseyn

Si vous souhaitez vous débarrasser de HttpClient, vous pouvez le configurer en tant que pool de ressources. Et à la fin de votre application, vous disposez de votre pool de ressources.

Code:

// Notice that IDisposable is not implemented here!
public interface HttpClientHandle
{
    HttpRequestHeaders DefaultRequestHeaders { get; }
    Uri BaseAddress { get; set; }
    // ...
    // All the other methods from peeking at HttpClient
}

public class HttpClientHander : HttpClient, HttpClientHandle, IDisposable
{
    public static ConditionalWeakTable<Uri, HttpClientHander> _httpClientsPool;
    public static HashSet<Uri> _uris;

    static HttpClientHander()
    {
        _httpClientsPool = new ConditionalWeakTable<Uri, HttpClientHander>();
        _uris = new HashSet<Uri>();
        SetupGlobalPoolFinalizer();
    }

    private DateTime _delayFinalization = DateTime.MinValue;
    private bool _isDisposed = false;

    public static HttpClientHandle GetHttpClientHandle(Uri baseUrl)
    {
        HttpClientHander httpClient = _httpClientsPool.GetOrCreateValue(baseUrl);
        _uris.Add(baseUrl);
        httpClient._delayFinalization = DateTime.MinValue;
        httpClient.BaseAddress = baseUrl;

        return httpClient;
    }

    void IDisposable.Dispose()
    {
        _isDisposed = true;
        GC.SuppressFinalize(this);

        base.Dispose();
    }

    ~HttpClientHander()
    {
        if (_delayFinalization == DateTime.MinValue)
            _delayFinalization = DateTime.UtcNow;
        if (DateTime.UtcNow.Subtract(_delayFinalization) < base.Timeout)
            GC.ReRegisterForFinalize(this);
    }

    private static void SetupGlobalPoolFinalizer()
    {
        AppDomain.CurrentDomain.ProcessExit +=
            (sender, eventArgs) => { FinalizeGlobalPool(); };
    }

    private static void FinalizeGlobalPool()
    {
        foreach (var key in _uris)
        {
            HttpClientHander value = null;
            if (_httpClientsPool.TryGetValue(key, out value))
                try { value.Dispose(); } catch { }
        }

        _uris.Clear();
        _httpClientsPool = null;
    }
}

var handler = HttpClientHander.GetHttpClientHandle (nouvel Uri ("base url")).

  • HttpClient, en tant qu'interface, ne peut pas appeler Dispose ().
  • Dispose () sera appelé de manière différée par le récupérateur de place. Ou lorsque le programme nettoie l'objet via son destructeur.
  • Utilise les références faibles + la logique de nettoyage différé afin qu’elle reste utilisée tant qu’elle est réutilisée fréquemment.
  • Il n'alloue qu'un nouveau HttpClient pour chaque URL de base qui lui est transmise. Les raisons expliquées par Ohad Schneider répondent ci-dessous. Mauvais comportement lors du changement d'URL de base.
  • HttpClientHandle permet de se moquer dans les tests
2
TamusJRoyce

L'utilisation de l'injection de dépendances dans votre constructeur facilite la gestion de la durée de vie de votre HttpClient - en prenant la gestion de la durée de vie en dehors du code qui en a besoin et en la rendant facilement modifiable ultérieurement.

Ma préférence actuelle est de créer une classe de client HTTP distincte qui hérite de HttpClient une fois par domaine de point de terminaison cible , puis en faire un singleton à l'aide de la dépendance. injection. public class ExampleHttpClient : HttpClient { ... }

Ensuite, je prends une dépendance de constructeur sur le client http personnalisé dans les classes de service où j'ai besoin d'accéder à cette API. Cela résout le problème de la durée de vie et présente des avantages en termes de regroupement de connexions.

Vous pouvez voir un exemple concret dans la réponse associée à l'adresse suivante: https://stackoverflow.com/a/50238944/314085

1
alastairtree

Comme il semble que personne ne l’ait déjà mentionné ici, la nouvelle meilleure façon de gérer HttpClient et HttpClientHandler dans .Net Core 2.1 consiste à utiliser HttpClientFactory .

Il résout la plupart des problèmes susmentionnés et se pose de manière propre et facile à utiliser. De l'excellent blog de Steve Gordon :

Ajoutez les packages suivants à votre projet .Net Core (2.1.1 ou version ultérieure):

Microsoft.AspNetCore.All
Microsoft.Extensions.Http

Ajoutez ceci à Startup.cs:

services.AddHttpClient();

Injecter et utiliser:

[Route("api/[controller]")]
public class ValuesController : Controller
{
    private readonly IHttpClientFactory _httpClientFactory;

    public ValuesController(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    [HttpGet]
    public async Task<ActionResult> Get()
    {
        var client = _httpClientFactory.CreateClient();
        var result = await client.GetStringAsync("http://www.google.com");
        return Ok(result);
    }
}

Explorez la série de publications sur le blog de Steve pour découvrir de nombreuses autres fonctionnalités.

1
pcdev

Pas besoin d'appeler Dispose, car HttpClient hérite de la classe HttpMessageInvoker et HttpMessageInvoker implémente l'interface IDisposal et HttpClientHandler hérite de la classe HttpMessageHandler et HttpMessageHandler implémente l'interface IDisposal

0
Sunil Dhappadhule