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

dka*_*man 37 .net c# idisposable stream dotnet-httpclient

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

此单元测试将失败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
}
Run Code Online (Sandbox Code Playgroud)

我通常会IDisposable尽可能方便地处理任何东西,但在这种情况下,处理它HttpResponseMessage也会处理Stream退回的东西ReadAsStreamAsync.

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

这个答案谈到不处理HttpClient.怎么样HttpRequestMessage和/或HttpResponseMessage

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

Yuv*_*kov 12

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

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

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

  • 这根本就不是什么好事!留下未处理的 HttpMessageRequest 和 HttpMessageResponse 对象意味着它们只会由 GC 处理(无论何时它可能决定运行),直到那时用于调用的所有资源都将被释放。如果并发 HTTP 请求有限制,这会导致端口耗尽、超时等...... (4认同)
  • @AlonCatz - GC **从不**自行调用 `.Dispose()` 。只有当有一个终结器调用“.Dispose()”时,GC 才能触发处置。 (4认同)
  • @AlonCatz - 完全不能保证终结器调用 `.Dispose()` - 只有在显式写入终结器时才会发生这种情况。 (3认同)

LP1*_*P13 7

您还可以将流作为输入参数,因此调用者可以完全控制流的类型以及它的处置.现在你也可以在控制离开方法之前配置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);                
        }
    }
Run Code Online (Sandbox Code Playgroud)


Cra*_*tti 7

在.NET中处理Dispose既容易又困难。当然。

流也拉扯了同样的废话...处置Buffer是否还会随后自动处置其包装的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的流。这些责任使人感到困惑吗?
  • 网络仍然是计算机系统中最慢的通道。阻止它们进行“优化”仍然是疯狂的。总有更好的方法来处理最慢的组件。

因此,请使用一次性使用的物品,例如“抓取并释放”……制作它们,自己抓取结果,并尽快释放它们。并且不要混淆优化的正确性,尤其是那些不是您自己编写的类。

  • CopyTo 不会枚举流吗?因此,我现在不再等待 HttpResponseMessage,而是将完整响应加载到内存中并从中创建一个新流? (4认同)