c# - ¿Cuándo o si hay que eliminar HttpResponseMessage al llamar a ReadAsStreamAsync?
static httpclient (3)
Estoy usando System.Net.Http.HttpClient
para hacer alguna comunicación HTTP del lado del cliente. Tengo todo el HTTP en un solo lugar, abstraído del resto del código. En un caso, quiero leer el contenido de la respuesta como una secuencia, pero el consumidor de la secuencia está bien aislado de donde ocurre la comunicación HTTP y se abre la corriente. En el lugar responsable de la comunicación HTTP, desecho todo lo relacionado con HttpClient
.
Esta prueba de unidad fallará en 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
}
Por lo general, trato de deshacerme de cualquier IDisposable
pueda IDisposable
a la mayor brevedad posible, pero en este caso, al eliminar el HttpResponseMessage
también se descarta el Stream
devuelto por ReadAsStreamAsync
.
Por lo tanto, parece que el código de llamada debe conocer y tomar posesión del mensaje de respuesta así como del flujo, o dejar el mensaje de respuesta sin contestar y dejar que el finalizador se ocupe de él. Ninguna de las opciones se siente bien.
Esta respuesta habla de no disponer el HttpClient
. ¿Qué tal el HttpRequestMessage
y / o HttpResponseMessage
?
¿Me estoy perdiendo de algo? Espero mantener el código consumido ignorante de HTTP, ¡pero dejar todos estos objetos no disueltos va en contra del año del hábito!
Por lo tanto, parece que el código de llamada debe conocer y tomar posesión del mensaje de respuesta así como del flujo, o dejar el mensaje de respuesta sin contestar y dejar que el finalizador se ocupe de él. Ninguna de las opciones se siente bien.
En este caso específico, no hay finalizadores . Ni HttpResponseMessage
ni HttpRequestMessage
implementan un finalizador (¡y eso es algo bueno!). Si no desecha a ninguno de ellos, se recogerá la basura una vez que se active el GC, y el identificador de sus flujos subyacentes se recogerá una vez que eso suceda.
Mientras esté utilizando estos objetos, no los deseche. Una vez hecho esto, deshazte de ellos . En lugar de envolverlos en una declaración using
, siempre puedes llamar explícitamente a Dispose
una vez que hayas terminado. De cualquier manera, el código consumidor no necesita tener ningún conocimiento subyacente a las solicitudes http.
También puede tomar el flujo como parámetro de entrada, de modo que la persona que llama tenga control completo sobre el tipo de flujo y su eliminación. Y ahora también puede eliminar httpResponse antes de que el control abandone el método.
A continuación se muestra el método de extensión para 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);
}
}
Tratar con los problemas en .NET es a la vez fácil y difícil. Sin lugar a duda.
Los flujos generan la misma tontería ... ¿Desechar el búfer también elimina automáticamente el flujo que contiene? ¿Deberia? Como consumidor, ¿debería siquiera saber si lo hace?
Cuando trato con estas cosas, sigo algunas reglas:
- Si creo que hay recursos no nativos en juego (como, ¡una conexión de red!), Nunca dejo que el GC "lo consiga". El agotamiento de los recursos es real, y un buen código lo trata.
- Si un Desechable toma un Desechable como parámetro, nunca hay daño en cubrir mi trasero y asegurarse de que mi código elimine todos los objetos que fabrica. Si mi código no lo hizo, puedo ignorarlo.
- GCs call ~ Finalize, pero nada garantiza que Finalize (es decir, su destructor personalizado) invoque Dispose. No hay magia, contrariamente a las opiniones anteriores, así que tienes que ser responsable de ello.
Entonces, tienes un HttpClient, un HttpRequestMessage y un HttpResponseMessage. Las vidas de cada uno de ellos, y de los Desechables que hagan, deben ser respetadas. Por lo tanto, nunca debe esperarse que su Stream salga del tiempo de vida Dispoable de HttpResponseMessage, ya que no creó una instancia del Stream.
En el escenario anterior, mi patrón sería pretender que obtener ese Stream fue realmente solo un método Static.DoGet (uri), y el Stream que devuelva TENDRÁ que ser uno de nuestros propios fabricantes. Eso significa un segundo Stream, con el stream de HttpResponseMessage .CopyTo''d my new Stream (enrutamiento a través de un FileStream o un MemoryStream o lo que mejor se adapte a su situación) ... o algo similar. Porque:
- No tienes derecho a la vida útil de HttpResponseMessage''s Stream. Eso es suyo, no tuyo. :)
- Mantener la vida útil de un desechable como HttpClient, mientras analizas el contenido de esa secuencia devuelta, es un bloqueador loco. Eso sería como mantenerte en una SqlConnection mientras analizas una DataTable (imagina lo rápido que moriríamos de un grupo de conexiones si las DataTables se hicieran enormes)
- Exponer cómo obtener esa respuesta puede funcionar contra SOLID ... Tenías un Stream, que es desechable, pero proviene de un HttpResponseMessage, que es desechable, pero eso solo sucedió porque usamos HttpClient y HttpRequestMessage, que son desechables .. . y todo lo que querías era una transmisión desde un URI. ¿Qué tan confusas se sienten esas responsabilidades?
- Las redes siguen siendo las vías más lentas en los sistemas informáticos. Sostenerlos para "optimización" sigue siendo una locura. Siempre hay mejores formas de manejar los componentes más lentos.
Así que use los desechables como captura y liberación ... hágalos, obtenga los resultados por usted mismo, libérelos lo más rápido posible. Y no confunda la optimización con la corrección, especialmente de las clases que usted mismo no creó.