System.Net.Http.HttpClient et System.Net.Http.HttpClientHandler dans .NET Framework 4.5 implémentent IDisposable (via System.Net.Http.HttpMessageInvoker ).
La documentation d’ using
instruction dit:
En règle générale, lorsque vous utilisez un object 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("foo", "bar"), new KeyValuePair("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 les commentaires de l’ annonce , quelqu’un a demandé à l’employé de Microsoft:
Après avoir vérifié vos échantillons, j’ai constaté que vous n’aviez pas effectué l’action disposer sur l’instance HttpClient. J’ai utilisé toutes les instances de HttpClient avec using des instructions sur mon application et j’ai pensé que c’était le bon moyen puisque HttpClient implémente l’interface IDisposable. Suis-je sur le bon chemin?
Sa réponse était:
En général, c’est correct même si vous devez faire attention à “utiliser” et asynchroniser car ils ne se mélangent pas vraiment dans .Net 4, dans .Net 4.5, vous pouvez utiliser “wait” dans une instruction “using”.
Btw, vous pouvez réutiliser le même HttpClient que vous le souhaitez, vous ne créerez / éliminerez donc pas tout le temps.
Le deuxième paragraphe est superflu par rapport à cette question, qui ne concerne pas le nombre de fois où vous pouvez utiliser une instance de HttpClient, mais s’il s’agit de savoir si vous devez en disposer une fois que vous n’en avez plus besoin.
(Mise à jour: en fait, le deuxième paragraphe est la clé de la réponse, comme indiqué ci-dessous par @DPeden.)
Donc mes questions sont:
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 éliminer, telles que des fuites de ressources ou des risques de corruption de données.
Si ce n’est pas nécessaire, serait-ce une “bonne pratique” de toute façon, puisqu’ils mettent en œuvre IDisposable?
Si c’est nécessaire (ou recommandé), ce code mentionné ci-dessus l’implémente-t-il en toute sécurité (pour .NET Framework 4.5)?
Si ces classes ne nécessitent pas d’appeler Dispose (), pourquoi ont-elles été implémentées comme IDisposable?
S’ils ont besoin, ou si c’est une pratique recommandée, les exemples Microsoft sont-ils trompeurs ou dangereux?
Le consensus général est que vous ne devez pas (ne devez pas) vous débarrasser de HttpClient.
Beaucoup de gens qui sont intimement impliqués dans la manière dont cela fonctionne ont déclaré cela.
Voir l’article du blog de Darrel Miller et un article de SO associé: HttpClient parsing les résultats en termes de fuite de mémoire pour référence.
Je vous suggère également fortement de lire le chapitre HttpClient de la section Conception d’API Web évolutives avec ASP.NET pour le contexte de ce qui se passe sous le capot, en particulier la section “Cycle de vie” citée ici:
Bien que HttpClient implémente indirectement l’interface IDisposable, l’utilisation standard de HttpClient ne consiste pas à s’en débarrasser après chaque requête. L’object HttpClient est conçu pour fonctionner aussi longtemps que votre application doit effectuer des requêtes HTTP. L’existence d’un object sur plusieurs requêtes permet de définir DefaultRequestHeaders et vous évite de devoir spécifier à nouveau des éléments tels que CredentialCache et CookieContainer pour chaque requête, comme cela était nécessaire avec HttpWebRequest.
Ou même ouvrir DotPeek.
Les réponses actuelles sont un peu confuses et trompeuses, et il manque certaines implications importantes du DNS. Je vais essayer de résumer les choses clairement.
IDisposable
doivent idéalement être éliminés lorsque vous en avez terminé , en particulier ceux qui possèdent des ressources de système d’exploitation nommé / partagé . HttpClient
ne fait pas exception à la règle, car, comme le fait remarquer Darrel Miller , il alloue des jetons d’annulation et les corps de demande / réponse peuvent être des stream non gérés. Connection:close
tête de Connection:close
après les modifications du DNS. Une autre possibilité consiste à recycler le HttpClient
du côté client, soit périodiquement, soit via un mécanisme qui apprend le changement de DNS. Voir https://github.com/dotnet/corefx/issues/11224 pour plus d’informations (je vous suggère de le lire attentivement avant d’utiliser aveuglément le code suggéré dans l’article du blog lié). À ma connaissance, l’appel de Dispose()
n’est nécessaire que lorsqu’il s’agit de verrouiller des ressources dont vous avez besoin ultérieurement (comme une connexion particulière). Il est toujours recommandé de libérer des ressources que vous n’utilisez plus, même si vous n’en avez plus besoin, simplement parce que vous ne devriez pas conserver les ressources que vous n’utilisez pas (jeu de mots).
L’exemple Microsoft n’est pas nécessairement correct. Toutes les ressources utilisées seront libérées à la fermeture de l’application. Et dans le cas de cet exemple, cela se produit presque immédiatement après l’ HttpClient
du HttpClient
. Dans les mêmes cas, appeler explicitement Dispose()
est quelque peu superflu.
Mais, en général, lorsqu’une classe implémente IDisposable
, il est entendu que vous devez Dispose()
de ses instances dès que vous êtes complètement prêt et capable. J’imagine que cela est particulièrement vrai dans des cas comme HttpClient
où il n’est pas explicitement documenté si les ressources ou les connexions sont conservées / ouvertes. Dans le cas où la connexion sera réutilisée à nouveau [bientôt], vous voudrez en renoncer à Dipose()
– vous n’êtes pas «complètement prêt» dans ce cas.
Voir aussi: Méthode IDisposable.Dispose et Quand appeler Dispose
Dispose () appelle le code ci-dessous, qui ferme les connexions ouvertes par l’instance HttpClient. Le code a été créé en décompilant avec dotPeek.
HttpClientHandler.cs – Dispose
ServicePointManager.CloseConnectionGroups(this.connectionGroupName);
Si vous n’appelez pas éliminer, ServicePointManager.MaxServicePointIdleTime, qui s’exécute par un minuteur, ferme 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.GetSsortingng("net_log_closed_idle", (object) "ServicePoint", (object) servicePoint.GetHashCode())); lock (ServicePointManager.s_ServicePointTable) ServicePointManager.s_ServicePointTable.Remove((object) servicePoint.LookupSsortingng); servicePoint.ReleaseAllConnectionGroups(); }
Si vous n’avez pas défini le temps d’inactivité à l’infini, il semble sûr de ne pas appeler disposer et laisser la timer de connexion inactive démarrer et fermer les connexions pour vous, bien qu’il soit préférable d’appeler disposer dans une instruction using si vous savez que vous avez terminé avec une instance HttpClient et libérez les ressources plus rapidement.
Dans mon cas, je créais un HttpClient dans une méthode qui effectuait l’appel de service. Quelque chose comme:
public void DoServiceCall() { var client = new HttpClient(); await client.PostAsync(); }
Dans un rôle de travail Azure, après avoir appelé cette méthode à plusieurs resockets (sans disposer du HttpClient), il échoue éventuellement avec SocketException
(la tentative de connexion a échoué).
J’ai fait du HttpClient une variable d’instance (au niveau de la classe) et le problème a disparu. Donc, je dirais oui, éliminez le HttpClient, en supposant qu’il soit sûr (vous n’avez pas d’appels asynchrones en attente) pour le faire.
En utilisation typique (réponses <2 Go), il n'est pas nécessaire de supprimer les messages HttpResponseMessages.
Les types de retour des méthodes HttpClient doivent être éliminés si leur contenu de stream n’est pas entièrement lu. Sinon, le CLR n’a aucun moyen de savoir que ces stream peuvent être fermés tant qu’ils ne sont pas nettoyés.
Si vous définissez le paramètre HttpCompletionOption sur ResponseHeadersRead ou si la réponse est supérieure à 2 Go, vous devez nettoyer. Cela peut être fait en appelant Dispose sur le HttpResponseMessage ou en appelant Dispose / Close sur le Stream obtenu à partir du Contenu HttpResonseMessage ou en lisant complètement le contenu.
Si vous appelez Dispose sur HttpClient, cela dépend si vous souhaitez ou non annuler les demandes en attente.
Si vous souhaitez éliminer HttpClient, vous pouvez le définir comme 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 _httpClientsPool; public static HashSet _uris; static HttpClientHander() { _httpClientsPool = new ConditionalWeakTable(); _uris = new HashSet (); 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 (“url de base”)).
L’utilisation de l’dependency injection dans votre constructeur facilite la gestion de la durée de vie de votre HttpClient
simplifie sa HttpClient
en dehors du code qui en a besoin et facilite sa modification ultérieure.
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 noeud final cible , puis en faire un singleton en utilisant l’dependency 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 pour le regroupement des connexions.
Vous pouvez voir un exemple concret dans la réponse associée à https://stackoverflow.com/a/50238944/3140853
Réponse courte: Non, l’affirmation dans la réponse actuellement acceptée n’est PAS exacte : “Le consensus général est que vous ne devez pas (ne devez pas) vous débarrasser de HttpClient”.
Réponse longue : Les deux déclarations suivantes sont vraies et réalisables en même temps:
IDisposable
est supposé / recommandé pour être éliminé. Et ils NE SONT PAS NECESSAIREMENT DE CONFLIT les uns avec les autres. Il suffit simplement d’organiser votre code pour réutiliser un HttpClient
ET de le disposer correctement.
Une réponse encore plus longue citée à partir de mon autre réponse :
Ce n’est pas une coïncidence de voir des personnes dans certains articles blâmer la façon HttpClient
l’interface IDisposable
HttpClient
les HttpClient
à utiliser le pattern using (var client = new HttpClient()) {...}
, puis à conduire à un problème de gestionnaire de socket épuisé.
Je crois que cela se résume à une conception tacite: “on s’attend à ce qu’un object identifiable soit de courte durée” .
CEPENDANT, alors que cela ressemble certainement à une chose éphémère quand on écrit du code dans ce style:
using (var foo = new SomeDisposableObject()) { ... }
la documentation officielle sur IDisposable ne mentionne jamais les objects IDisposable
doivent être de courte durée. Par définition, IDisposable est simplement un mécanisme permettant de libérer des ressources non managées. Rien de plus. En ce sens, vous DEVEZ é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 à quel moment déclencher l’élimination, en fonction des exigences du cycle de vie de votre object réel. Rien ne vous empêche d’utiliser un identifiant 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, nous revenons maintenant sur 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 sur sa sortie netstat que la connexion rest à l’état ESTABLISHED N’a PAS été correctement fermé. S’il était fermé, son état serait plutôt dans TIME_WAIT. Dans la pratique, il n’est pas très important de laisser une seule connexion ouverte après la fin de votre programme, et l’affiche du blog affiche toujours un gain de performance après le correctif; mais quand même, il est conceptuellement incorrect de blâmer l’IDisposable et de choisir de ne PAS s’en débarrasser.
Je pense que l’on devrait utiliser le modèle singleton pour éviter de 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 le modèle de singleton, cliquez ici .
class HttpClientSingletonWrapper : HttpClient { private static readonly Lazy Lazy= new Lazy (()=>new HttpClientSingletonWrapper()); public static HttpClientSingletonWrapper Instance {get { return Lazy.Value; }} private HttpClientSingletonWrapper() { } }
Utilisez le code ci-dessous.
var client = HttpClientSingletonWrapper.Instance;