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

时间:2014-12-31 02:17:30

标签: c# .net stream idisposable 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
}

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

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

This answer谈到没有处置HttpClientHttpRequestMessage和/或HttpResponseMessage

怎么样?

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

4 个答案:

答案 0 :(得分:10)

  

所以看起来调用代码需要知道并采取   响应消息以及流的所有权,或者我离开   反应消息不受干扰,让终结者处理它。   这两种选择都不对。

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

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

答案 1 :(得分: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);                
        }
    }

答案 2 :(得分:4)

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

流同样会废话……处置Buffer还会自动处置包裹的Stream吗?应该是?作为消费者,我什至不知道是否这样做吗?

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

  1. 如果我认为有非本地资源(例如网络连接!)在起作用,那么我绝对不要让GC“解决它”。资源耗尽是真实的,好的代码可以解决这个问题。
  2. 如果Disposable将Disposable作为参数,那么覆盖我的屁股并确保我的代码可以丢弃它所产生的每个对象都不会有任何危害。如果我的代码没有成功,我可以忽略它。
  3. GC调用〜Finalize,但没有任何方法可以保证Finalize(即您的自定义析构函数)调用Dispose。与上面的观点相反,没有魔术,因此您必须对此负责。

因此,您有一个HttpClient,一个HttpRequestMessage和一个HttpResponseMessage。必须尊重它们每个生命周期以及它们制成的任何一次性用品。因此,永远不要期望您的Stream在HttpResponseMessage的Disposable生命周期之外生存,因为没有实例化Stream。

在上述情况下,我的模式是假装获取该Stream实际上只是在Static.DoGet(uri)方法中,并且您返回的Stream将是我们自己创建的。这意味着第二个Stream,带有HttpResponseMessage的stream .CopyTo'd我的新Stream(通过FileStream或MemoryStream或最适合您的情况的路由)...或类似的东西。因为:

  • 您无权使用HttpResponseMessage的Stream的生存期。那是他的,不是你的。 :)
  • 在处理返回的流的内容时,延长HttpClient这样的一次性对象的寿命是一个疯狂的阻止者。就像在解析DataTable时保持SqlConnection(想象一下,如果DataTable变得庞大,我们将使连接池饿死多久)
  • 公开如何可能会针对SOLID起作用...您有一个Stream,它是可抛弃的,但它来自HttpResponseMessage,它是可抛弃的,但这仅是因为我们使用了HttpClient和HttpRequestMessage,它们都是可抛弃的……而您想要的只是来自URI的流。这些责任使人感到困惑吗?
  • 网络仍然是计算机系统中最慢的通道。阻止它们进行“优化”仍然是疯狂的。总有更好的方法来处理最慢的组件。

因此,请使用一次性使用的物品,例如捕捉和释放...,让它们自己抓住结果,并尽快释放它们。并且不要混淆优化的正确性,尤其是那些不是您自己编写的类。

答案 3 :(得分:0)

不要处理 HttpResponseMessage,因为这是调用此方法的一方的职责。

方法:

public async Task<Stream> GetStreamContentOrNullAsync()
{
    // The response will be disposed when the returned content stream is disposed.
    const string url = "https://myservice.com/file.zip";
    var client = new HttpClient(); //better use => var httpClient = _cliHttpClientFactory.CreateClient();
    var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
    if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
    {
        return null;
    }

    return await response.Content.ReadAsStreamAsync();
}

用法:

  public async Task<IActionResult> DownloadPackageAsync()
  {
      var stream = await GetStreamContentOrNullAsync();
      if (stream == null)
      {
            return NotFound();
      }

      return File(stream, "application/octet-stream");
  }