在调用ReadAsStreamAsync时何时或何时调用HttpResponseMessage?

我使用System.Net.Http.HttpClient进行一些客户端HTTP通信。 我在一个地方得到了所有的HTTP,从其余的代码中抽象出来。 在一个实例中,我希望将响应内容作为流读取,但是流的使用者与HTTP通信发生的位置以及流被打开的情况很好地隔离。 在负责HTTP通信的地方,我正在处理所有的HttpClient内容。

Assert.IsTrue(stream.CanRead)此unit testing将失败:

 [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 } 

我通常会尽可能方便地处理任何IDisposable ,但在这种情况下,处理HttpResponseMessage还会处理从ReadAsStreamAsync返回的Stream

所以看起来调用代码需要了解并获取响应消息以及流的所有权,或者我保持响应消息不受阻碍并让终结器处理它。 这两种选择都不对。

这个答案谈到没有处理HttpClientHttpRequestMessage和/或HttpResponseMessage怎么样?

我错过了什么吗? 我希望保持消费代码不知道HTTP,但留下所有这些不受约束的对象违背了一年的习惯!

所以看起来调用代码需要了解并获取响应消息以及流的所有权,或者我保持响应消息不受阻碍并让终结器处理它。 这两种选择都不对。

在这种特定情况下, 没有终结器HttpResponseMessageHttpRequestMessage都没有实现终结器(这是一件好事!)。 如果你不处理它们中的任何一个,它们将在GC启动后收集垃圾,并且一旦发生这种情况就会收集其底层流的句柄。

只要您使用这些物品,请不要丢弃。 完成后, 处理它们 。 您可以在完成后始终显式调用Dispose ,而不是将它们包装在using语句中。 无论哪种方式,消费代码都不需要具有任何基于http请求的知识。

您还可以将流作为输入参数,因此调用者可以完全控制流的类型以及它的处理方式。 现在你也可以在控制离开方法之前配置httpResponse。
下面是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); } } 

在.NET中处理Disposes既简单又困难。 当然。

Streams拉同样的废话……处理缓冲区然后自动处理它包裹的Stream吗? 应该是? 作为消费者,我是否应该知道它是否存在?

当我处理这些东西时,我会遵循一些规则:

  1. 如果我认为有非本地资源在运行(比如网络连接!),我就不会让GC“绕过它”。 资源耗尽是真实的,好的代码处理它。
  2. 如果Disposable将Disposable作为参数,那么覆盖我的屁股并确保我的代码处理它所生成的每个对象都不会有害。 如果我的代码没有成功,我可以忽略它。
  3. GC调用~Finalize,但没有任何东西可以保证Finalize(即你的自定义析构函数)调用Dispose。 没有魔法,与上述观点相反,所以你必须对此负责。

所以,你有一个HttpClient,一个HttpRequestMessage和一个HttpResponseMessage。 他们每个人的生命以及他们所做的任何一次性生活必须得到尊重。 因此,不应期望您的Stream在HttpResponseMessage的Dispoable生命周期之外存活,因为没有实例化Stream。

在上面的场景中,我的模式是假装获取Stream实际上只是在Static.DoGet(uri)方法中,并且您返回的Stream必须是我们自己制作的。 这意味着第二个流,带有HttpResponseMessage的流.CopyTo’d我的新流(通过FileStream或MemoryStream路由或任何最适合您的情况)……或类似的东西。 因为:

  • 你没有权利使用HttpResponseMessage的Stream。 那是他的,不是你的。 🙂
  • 保持像HttpClient这样的一次性用品的生命周期,同时你处理返回的流的内容,是一个疯狂的阻止者。 这就像在解析DataTable时保持一个SqlConnection(想象一下,如果DataTables变得庞大,我们会多么快地饿死连接池)
  • 揭示获得该响应的方式可能对SOLID有效……你有一个Stream,它是一次性的,但它来自HttpResponseMessage,这是一次性的,但这只是因为我们使用HttpClient和HttpRequestMessage,这是一次性的.. 。你想要的只是来自URI的流。 这些责任感到多么混乱?
  • 网络仍然是计算机系统中最慢的通道。 坚持他们进行“优化”仍然是疯了。 总有更好的方法来处理最慢的组件。

因此,使用像捕获和释放一样的一次性物品……制作它们,为自己抓住结果,尽快释放它们。 并且不要混淆优化的正确性,特别是你自己没有创作的类。