Quand ou si éliminer HttpResponseMessage lors de l’appel de ReadAsStreamAsync?

J’utilise System.Net.Http.HttpClient pour effectuer certaines communications HTTP côté client. J’ai tout le HTTP dans un endroit, abstraite du rest du code. Dans un cas, je souhaite lire le contenu de la réponse sous forme de stream, mais le consommateur du stream est bien isolé de l’endroit où la communication HTTP a lieu et le stream est ouvert. À la place responsable de la communication HTTP, je HttpClient de tous les trucs HttpClient .

Ce test unitaire échouera dans Assert.IsTrue(stream.CanRead) :

 [TestMethod] public async Task DebugStreamedContent() { Stream stream = null; // in real life the consumer of the stream is far away var client = new HttpClient(); client.BaseAddress = new Uri("https://www.google.com/", UriKind.Absolute); using (var request = new HttpRequestMessage(HttpMethod.Get, "/")) using (var response = await client.SendAsync(request)) { response.EnsureSuccessStatusCode(); //here I would return the stream to the caller stream = await response.Content.ReadAsStreamAsync(); } Assert.IsTrue(stream.CanRead); // FAIL if response is disposed so is the stream } 

J’essaie généralement de disposer de tout ce qui est IDisposable le plus tôt possible, mais dans ce cas, le HttpResponseMessage dispose également du Stream renvoyé par ReadAsStreamAsync .

Il semble donc que le code appelant doit connaître et s’approprier le message de réponse ainsi que le stream, ou que je laisse le message de réponse sans être exposé et que le finaliseur s’en charge. Aucune option ne semble juste.

Cette réponse parle de ne pas disposer du HttpClient . Que diriez-vous de HttpRequestMessage et / ou HttpResponseMessage ?

Est-ce que je manque quelque chose? J’espère garder le code consommateur ignorant HTTP, mais le fait de laisser tous ces objects non déplacés circule contre une année d’habitude!

Il semble donc que le code appelant doit connaître et s’approprier le message de réponse ainsi que le stream, ou que je laisse le message de réponse sans être exposé et que le finaliseur s’en charge. Aucune option ne semble juste.

Dans ce cas spécifique, il n’y a pas de finaliseurs . Ni HttpResponseMessage ni HttpRequestMessage HttpResponseMessage un finaliseur (et c’est une bonne chose!). Si vous ne disposez pas de l’un d’eux, ils seront récupérés une fois que le GC sera entré en jeu, et le descripteur de leurs stream sous-jacents sera collecté une fois que cela se produira.

Tant que vous utilisez ces objects, ne vous en débarrassez pas. Une fois cela fait, éliminez-les . Au lieu de les envelopper dans une instruction using , vous pouvez toujours appeler explicitement Dispose une fois que vous avez terminé. De toute façon, le code consommateur n’a pas besoin de connaître les requêtes http.

Vous pouvez également prendre le stream en tant que paramètre d’entrée afin que l’appelant ait le contrôle total sur le type du stream ainsi que sur son élimination. Et maintenant, vous pouvez également disposer de httpResponse avant que le contrôle ne quitte la méthode.
Ci-dessous la méthode d’extension pour HttpClient

  public static async Task HttpDownloadStreamAsync(this HttpClient httpClient, ssortingng url, Stream output) { using (var httpResponse = await httpClient.GetAsync(url).ConfigureAwait(false)) { // Ensures OK status response.EnsureSuccessStatusCode(); // Get response stream var result = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); await result.CopyToAsync(output).ConfigureAwait(false); output.Seek(0L, SeekOrigin.Begin); } } 

Traiter avec Dispose dans .NET est à la fois facile et difficile. Pour sûr.

Les stream tirent ce même non-sens … L’élimination de la mémoire tampon supprime-t-elle également automatiquement le stream qu’il a enveloppé? Devrait-il? En tant que consommateur, devrais-je même savoir si c’est le cas?

Quand je traite de ce genre de choses, je respecte certaines règles:

  1. Que si je pense qu’il y a des ressources non-natives en jeu (comme une connexion réseau!), Je ne laisse jamais le GC “se débrouiller”. L’épuisement des ressources est réel, et un bon code en traite.
  2. Si un object jetable prend un object jetable en tant que paramètre, il n’y a jamais de mal à couvrir mes fesses et à m’assurer que mon code supprime chaque object fabriqué. Si mon code n’a pas été créé, je peux l’ignorer.
  3. Les GC appellent ~ Finalize, mais rien ne garantit jamais que Finalize (c’est-à-dire votre destructeur personnalisé) invoque Dispose. Il n’y a pas de magie, contrairement aux opinions ci-dessus, vous devez donc en être responsable.

Vous avez donc un HttpClient, un HttpRequestMessage et un HttpResponseMessage. La durée de vie de chacun d’entre eux, ainsi que tout le matériel jetable qu’ils produisent, doit être respectée. Par conséquent, votre stream ne devrait jamais survivre en dehors de la durée de vie indéfinissable de HttpResponseMessage, car vous n’avez pas instancié le stream.

Dans le scénario ci-dessus, mon schéma serait de prétendre que l’obtention de ce stream n’était en réalité qu’une méthode Static.DoGet (uri), et le stream que vous retourneriez DOIT en faire partie. Cela signifie un deuxième stream, avec le stream de HttpResponseMessage. Copier mon nouveau stream (routage via FileStream, MemoryStream ou autre, selon votre situation) … ou quelque chose de similaire. Parce que:

  • Vous n’avez pas droit à la durée de vie du stream HttpResponseMessage. C’est à lui, pas à vous. 🙂
  • Tenir la durée de vie d’un jetable comme HttpClient pendant que vous écrasez le contenu de ce stream renvoyé est un bloqueur fou. Ce serait comme conserver un SqlConnection pendant que vous parsingz un DataTable (imaginez la rapidité avec laquelle nous mourrons de faim d’un pool de connexion si les DataTables devenaient énormes)
  • Dénoncer le moyen d’obtenir cette réponse peut avoir un effet négatif sur SOLID … Vous aviez un stream jetable, mais il provenait d’un message HttpResponseMessage, qui était disponible, mais qui ne s’était produit que parce que nous avions utilisé HttpClient et HttpRequestMessage, qui sont jetables. . et tout ce que vous vouliez était un stream d’URI. À quel point ces responsabilités sont-elles déroutantes?
  • Les réseaux restnt les voies les plus lentes des systèmes informatiques. Les tenir pour “l’optimisation” est encore fou. Il y a toujours de meilleures façons de gérer les composants les plus lents.

Donc, utilisez des produits jetables comme «catch-and-release»… produisez-les, accrochez-vous des résultats, libérez-les le plus rapidement possible. Et ne confondez pas optimisation pour exactitude, en particulier pour les classes que vous n’avez pas vous-même écrites.