Est-ce que HttpClient et HttpClientHandler doivent être éliminés?

System.Net.Http.Système HttpClient et .Net.Http.HttpClientHandler dans.NET Framework 4.5 implémenter IDisposable (via le système ).Net.Http.HttpMessageInvoker ).

la documentation de la déclaration using dit:

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

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() explicitement ou implicitement. Par exemple:

dans le annonce 's commentaires, quelqu'un a demandé à L'employé de Microsoft:

après avoir vérifié vos échantillons, j'ai vu que vous n'aviez pas effectué l'élimination action sur l'instance HttpClient. J'ai utilisé toutes les instances de HttpClient avec l'aide de déclaration sur mon application et j'ai pensé que c'est la bonne façon depuis HttpClient implémente L'interface IDisposable. Suis-je sur la droit chemin?

sa réponse était:

En général c'est correct, bien que vous devez être prudent avec les "utiliser" et async car ils ne se mélangent pas vraiment dans .Net 4, dans .net 4.5 vous peut utiliser " attendre "dans une instruction" utiliser".

Btw, vous pouvez réutiliser le même HttpClient autant de fois que vous le souhaitez en général, vous ne les créerez pas / n'en disposerez pas tout le temps.

le deuxième paragraphe est superflu pour cette question, qui ne se préoccupe pas du nombre de fois où vous pouvez utiliser une instance HttpClient, mais de savoir s'il est nécessaire de l'éliminer après que vous n'en avez plus besoin.

(mise à Jour: en fait, c', deuxième alinéa, est la clé de la réponse, comme prévu ci-dessous par @DPeden.)

donc mes questions sont:

  1. est-il nécessaire, compte tenu de 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 à ne pas se départir, telles que des fuites de ressources ou des risques de corruption de données.

  2. si ce n'est pas nécessaire, "bonne pratique" de toute façon, puisqu'ils mettent en œuvre IDisposable?

  3. si c'est nécessaire (ou recommandé), est-ce que ce code mentionné ci-dessus l'implémente en toute sécurité (pour .net Framework 4.5)?

  4. si ces classes n'ont pas besoin d'appeler Dispose(), pourquoi ont-elles été implémentées comme IDisposable?

  5. S'ils ont besoin, ou si c'est un recommandé pratique, Microsoft exemples trompeuse ou dangereux?

257
demandé sur robbie fan 2013-03-29 18:17:56

11 réponses

le consensus général est que vous n'avez pas (ne devriez 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 Darrel Miller blog , une SORTE de post: HttpClient ramper résultats de fuite de mémoire pour référence.

je vous conseille vivement de lire le chapitre de HttpClient de Conception Évolutives Api Web avec ASP.NET pour le contexte de ce qui se passe sous le capot, en particulier le "Cycle de vie" de l'article cité:

bien que HttpClient mette indirectement en œuvre L'IDisposable interface, L'utilisation standard de HttpClient n'est pas d'en disposer après chaque demande. L'objet HttpClient est destiné à vivre pour que tant que votre application a besoin de faire des requêtes HTTP. Avoir objet exister à travers des demandes multiples permet à un endroit de mise en DefaultRequestHeaders et vous évite d'avoir à re-spécifier des choses comme CredentialCache et CookieContainer sur chaque demande comme était nécessaire avec HttpWebRequest.

ou même ouvrir DotPeek.

201
répondu David Peden 2017-05-23 12:26:35

les réponses actuelles sont un peu confuses et trompeuses, et elles passent à côté de certaines implications importantes du DNS. Je vais essayer de résumer clairement la situation.

  1. en général la plupart IDisposable les objets devraient idéalement être éliminés lorsque vous en avez terminé avec eux , en particulier ceux qui propres ressources OS nommées/partagées . HttpClient ne fait pas exception, puisque comme Darrel Miller souligne qu'il attribue des jetons d'Annulation, et les organismes de requête/réponse peuvent être des flux non gérés.
  2. cependant, le best practice for HttpClient dit que vous devez créer une instance et la réutiliser autant que possible (en utilisant son thread-safe members dans des scénarios multi-threads). Par conséquent, dans la plupart des scénarios vous ne pourrez jamais en disposer simplement parce que vous en aurez besoin tout le temps .
  3. le problème avec la réutilisation du même HttpClient" forever "est que la connexion HTTP sous-jacente pourrait rester ouverte par rapport à L'IP initialement résolue par DNS, indépendamment des changements DNS . Cela peut poser problème dans des scénarios comme le déploiement bleu/vert de et le basculement basé sur le DNS de . Il existe différentes approches pour traiter ce problème, la plus fiable impliquant le serveur qui envoie un en-tête Connection:close après les changements DNS ont lieu. Une autre possibilité consiste à recycler le HttpClient du côté du client, soit périodiquement, soit par un mécanisme qui apprend le 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 le lien blog post).
21
répondu Ohad Schneider 2017-07-08 10:21:21

à mon avis, appeler Dispose() n'est nécessaire que lorsque vous avez besoin de ressources de verrouillage plus tard (comme une connexion particulière). Il est toujours recommandé pour 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 tenir sur les ressources que vous n'utilisez pas (Jeu de mots prévu).

L'exemple de Microsoft n'est pas nécessairement incorrect. Toutes les ressources utilisé sera libéré lorsque l'application sort. Et dans le cas de cet exemple, cela se produit presque immédiatement après l'utilisation du HttpClient . Dans des 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 c'est particulièrement vrai dans les cas comme HttpClient où il n'y a pas de documentation explicite indiquant si les ressources ou les liens sont maintenus/ouverts. Dans le cas où la connexion sera réutilisée [bientôt], vous voudrez renoncer Dipose() ing de celui-ci-vous n'êtes pas "entièrement prêt" dans ce cas.

voir aussi: IDisposable.Méthode d'élimination et quand appeler éliminer

16
répondu svidgen 2013-04-10 15:31:11

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

HttpClientHandler.cs-Eliminer 151930920"

ServicePointManager.CloseConnectionGroups(this.connectionGroupName);

si vous n'appelez pas disposal puis ServicePointManager.MaxServicePointIdleTime, qui s'exécute à l'aide d'une minuterie, fermera les connexions http. La valeur par défaut est de 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 inactif à infinite, alors il semble sûr de ne pas appeler dispose et de laisser le temps de connexion inactif démarrer et fermer les connexions pour vous, bien qu'il serait préférable pour vous d'appeler dispose dans une déclaration d'utilisation si vous savez que vous avez terminé avec une instance HttpClient et libérer les ressources plus rapidement.

7
répondu Timothy Gonzalez 2017-10-26 22:49:51

dans mon cas, je créais un HttpClient à l'intérieur d'une méthode qui a fait l'appel de service. Quelque chose comme:

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

dans un rôle d'ouvrier D'Azur, après avoir appelé à plusieurs reprises cette méthode (sans disposer du HttpClient), elle finirait par échouer avec SocketException (tentative de connexion échouée).

j'ai fait du Httpcclient une variable d'instance (la disposant au niveau de la classe) et la question a disparu. Donc je dirais que, oui, d'en disposer le client HttpClient, en supposant qu'il est en sécurité (vous n'avez pas d'appels asynchrones en attente) pour le faire.

4
répondu David Faivre 2013-04-26 14:06:31

dans l'usage courant (réponses<2 Go), il n'est pas nécessaire de disposer des messages HttpResponse.

les types de retour des méthodes HttpClient doivent être éliminés si leur contenu N'est pas entièrement lu. Autrement, il n'y a aucun moyen pour le PLC de savoir que ces cours d'eau peuvent être fermés tant qu'ils ne sont pas ramassés.

  • si vous lisez les données dans un octet[] (par exemple GetByteArrayAsync) ou une chaîne, toutes les données sont lues, donc il n'y a pas vous devez mettre au rebut.
  • les autres surcharges seront par défaut pour lire le flux jusqu'à 2 Go (Httpcomplletionoption is Responsiecontentread, HttpClient.MaxResponseContentBufferSize par défaut est de 2 go)

si vous définissez L'option Httpcomplleoption à Responsieheadersread ou si la réponse est supérieure à 2 Go, vous devez nettoyer. Ceci peut être fait en appelant Dispose sur le serveur HttpResponseMessage ou en appelant Dispose/Close sur le flux obtenu à partir du HttpResonseMessage le contenu ou en lisant le contenu complètement.

que vous appeliez Dispose sur le HttpClient dépend si vous voulez annuler des requêtes en attente ou non.

3
répondu Tom Deseyn 2013-12-26 21:40:51

si vous voulez disposer de HttpClient, vous pouvez le configurer comme un pool de ressources. Et à la fin de votre demande, vous disposez de votre réserve 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 (New Uri ("base url")).

  • HttpClient, en tant qu'interface, ne peut pas appeler Dispose().
  • Dispose () sera appelé de manière retardée par les ordures Collecteur. Ou quand le programme nettoie l'objet à travers son destructeur.
  • utilise des références faibles + une logique de nettoyage différé pour qu'il reste utilisé tant qu'il est réutilisé fréquemment.
  • il n'attribue qu'un nouveau HttpClient pour chaque URL de base qui lui est transmise. Raisons expliquées par Ohad Schneider réponse ci-dessous. Mauvais comportement lors du changement d'url de base.
  • HttpClientHandle permet de se moquer dans les tests
0
répondu TamusJRoyce 2018-02-27 15:37:15

L'utilisation de l'injection de dépendances dans votre constructeur rend la gestion de la durée de vie de votre HttpClient plus facile - prendre la gestion de la durée de vie en dehors du code qui en a besoin et la rendre facilement modifiable à une date ultérieure.

Ma préférence actuelle est de créer une classe de client http séparé qui hérite de HttpClient une fois par Domaine cible endpoint et ensuite en faire un seul en utilisant l'injection de dépendances. public class ExampleHttpClient : HttpClient { ... }

puis 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 matière de mise en commun des connexions.

vous pouvez voir un exemple travaillé dans la réponse connexe à https://stackoverflow.com/a/50238944/3140853

0
répondu alastairtree 2018-05-08 17:37:16

brève réponse: 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 (ne devriez pas) avoir à disposer de HttpClient".

longue réponse : les deux affirmations suivantes sont vraies et réalisables en même temps:

  1. "HttpClient est conçu pour être instancié une fois et ré-utilisé tout au long de la durée de vie d'une application", extrait de documentation officielle .
  2. un IDisposable objet supposé/recommandé à éliminer.

et ils ne sont pas nécessairement en conflit les uns avec les autres. Il s'agit simplement de la façon dont vous organisez votre code pour réutiliser un HttpClient et l'éliminer correctement.

encore réponds plus , cité par mon autre réponse : comme une chose de courte durée quand nous écrivons le code dans ce style:

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

le documentation officielle sur IDisposable ne mentionne jamais IDisposable objets doivent être de courte durée. Par définition, IDisposable est simplement un mécanisme qui vous permet de libérer des ressources non gérées. Rien de plus. En ce sens, vous êtes censé éventuellement déclencher l'élimination, mais cela ne vous oblige pas à le faire de façon éphémère.

c'est donc votre travail de bien choisir quand déclencher l'élimination, base sur le 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 que le billet de blog , nous pouvons clairement remarquer que le " fix "initialise HttpClient une fois mais ne l'élimine jamais, c'est pourquoi nous pouvons voir à partir de son résultat netstat que, le la connexion reste à L'état établi, ce qui signifie qu'elle n'a pas été correctement fermée. S'il était fermé, son état serait dans TIME_WAIT à la place. En pratique, ce n'est pas une grosse affaire de ne laisser filtrer qu'une seule connexion ouverte après la fin de votre programme complet., et l'affiche de blog encore voir un gain de performance après le fix; mais encore, il est conceptuellement incorrect de blâmer IDisposable et choisir de ne pas en disposer.

0
répondu RayLuo 2018-05-31 20:19:22

pas besoin D'appeler Dispose Parce que HttpClient hérite de la classe HttpMessageInvoker et de L'Interface HttpMessageInvoker implement IDisposal et HttpClientHandler heterite HttpMessageHandler class et HttpMessageHandler implement IDisposal Interface

0
répondu Sunil Dhappadhule 2018-10-11 13:15:41

je pense que l'on devrait utiliser le modèle singleton pour éviter d'avoir à créer des instances du HttpClient et de le fermer tout le temps. Si vous utilisez .Net 4.0, vous pouvez utiliser un exemple de code comme ci-dessous. pour plus d'informations sur singleton pattern, consultez ici .

class HttpClientSingletonWrapper : HttpClient
{
    private static readonly Lazy<HttpClientSingletonWrapper> Lazy= new Lazy<HttpClientSingletonWrapper>(()=>new HttpClientSingletonWrapper()); 

    public static HttpClientSingletonWrapper Instance {get { return Lazy.Value; }}

    private HttpClientSingletonWrapper()
    {
    }
}

utilisez le code ci-dessous.

var client = HttpClientSingletonWrapper.Instance;
-3
répondu yayadavid 2017-06-11 15:44:48