web-dev-qa-db-fra.com

Quand ou s'il faut éliminer HttpResponseMessage lors de l'appel de ReadAsStreamAsync?

J'utilise System.Net.Http.HttpClient pour effectuer des communications HTTP côté client. J'ai tout le HTTP en un seul endroit, éloigné du reste du code. Dans un cas, je veux lire le contenu de la réponse sous forme de flux, mais le consommateur du flux est bien isolé de l'endroit où la communication HTTP se produit et le flux est ouvert. A l'endroit responsable de la communication HTTP, je dispose de tout le HttpClient.

Ce test unitaire échouera à 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 d'éliminer tout ce qui est IDisposable le plus tôt possible, mais dans ce cas, l'élimination du HttpResponseMessage supprime également le Stream renvoyé de ReadAsStreamAsync.

Il semble donc que le code appelant doive connaître et s'approprier le message de réponse ainsi que le flux, ou je laisse le message de réponse non exposé et laisse le finaliseur s'en occuper. Aucune des deux options ne semble appropriée.

Cette réponse parle de ne pas disposer du HttpClient. Qu'en est-il des HttpRequestMessage et/ou HttpResponseMessage?

Suis-je en train de manquer quelque chose? J'espère garder le code consommateur ignorant de HTTP mais laisser tous ces objets non disposés va à l'encontre de l'année d'habitude!

33
dkackman

Il semble donc que le code appelant doit connaître et s'approprier le message de réponse ainsi que le flux, ou je laisse le message de réponse non exposé et laisse le finaliseur s'en occuper. Aucune des deux options ne semble appropriée.

Dans ce cas spécifique, il n'y a pas de finaliseurs. Ni HttpResponseMessage ni HttpRequestMessage n'implémentent un finaliseur (et c'est une bonne chose!). Si vous ne disposez pas de l'un d'eux, ils récupèreront les ordures une fois que le GC entre en jeu, et la poignée de leurs flux sous-jacents sera collectée une fois que cela se produira.

Tant que vous utilisez ces objets, ne les jetez pas. Une fois terminé, jetez-les. Au lieu de les encapsuler dans une instruction using, vous pouvez toujours appeler explicitement Dispose une fois que vous avez terminé. Dans tous les cas, le code consommateur n'a pas besoin de connaître les requêtes http sous-jacentes.

11
Yuval Itzchakov

Vous pouvez également prendre le flux comme paramètre d'entrée, afin que l'appelant ait un contrôle complet sur le type du flux ainsi que sur son élimination. Et maintenant, vous pouvez également supprimer 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, string 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);                
        }
    }
7
LP13

Gérer les suppressions dans .NET est à la fois facile et difficile. Pour sûr.

Les flux tirent ce même non-sens ... Est-ce que l'élimination du tampon élimine également automatiquement le flux qu'il a enveloppé? Devrait-il? En tant que consommateur, devrais-je même savoir si c'est le cas?

Quand je m'occupe 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 "y arriver". L'épuisement des ressources est réel et un bon code s'en occupe.
  2. Si un jetable prend un jetable comme paramètre, il n'y a jamais de mal à couvrir mes fesses et à s'assurer que mon code dispose de tous les objets qu'il fabrique. Si mon code n'a pas réussi, je peux l'ignorer.
  3. Les GC appellent ~ Finalize, mais rien ne garantit 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.

Donc, vous avez un HttpClient, un HttpRequestMessage et un HttpResponseMessage. La durée de vie de chacun d'eux, et tout jetable qu'ils font, doivent être respectés. Par conséquent, votre Stream ne devrait jamais survivre en dehors de la durée de vie Dispoable de HttpResponseMessage, car vous n'a pas instancié le Stream.

Dans votre scénario ci-dessus, mon modèle serait de prétendre que l'obtention de ce flux était vraiment juste dans une méthode Static.DoGet (uri), et le flux que vous retournez DEVRAIT être une de nos propres créations. Cela signifie un deuxième Stream, avec le flux de HttpResponseMessage .CopyTo'd mon nouveau Stream (routage via un FileStream ou un MemoryStream ou tout ce qui convient le mieux à votre situation) ... ou quelque chose de similaire. Car:

  • Vous n'avez pas droit à la durée de vie du flux de HttpResponseMessage. C'est le sien, pas le vôtre. :)
  • Retenir la durée de vie d'un jetable comme HttpClient, pendant que vous crunch le contenu de ce flux renvoyé, est un bloqueur fou. Ce serait comme conserver une SqlConnection pendant que vous analysez un DataTable (imaginez à quelle vitesse nous affamerions un pool de connexions si les DataTables devenaient énormes)
  • Exposer le comment pour obtenir cette réponse peut fonctionner contre SOLID ... Vous aviez un Stream, qui est jetable, mais il provenait d'un HttpResponseMessage, qui est jetable, mais cela ne s'est produit que parce que nous avons utilisé HttpClient et HttpRequestMessage, qui sont jetables ... et tout ce que vous vouliez était un flux à partir d'un URI. À quel point ces responsabilités sont-elles confuses?
  • Les réseaux sont toujours les voies les plus lentes dans les systèmes informatiques. Les maintenir pour "l'optimisation" est toujours fou. Il existe toujours de meilleures façons de gérer les composants les plus lents.

Alors utilisez des produits jetables comme le catch-and-release ... faites-les, accrochez-vous les résultats par vous-même, libérez-les le plus rapidement possible. Et ne confondez pas l'optimisation et l'exactitude, en particulier dans les classes que vous n'avez pas créées vous-même.

6
Craig Brunetti