【问题标题】:Can I directly stream from HttpResponseMessage to file without going through memory?我可以直接从 HttpResponseMessage 流式传输到文件而不通过内存吗?
【发布时间】:2016-12-26 07:30:36
【问题描述】:

我的程序使用HttpClient 向 Web API 发送 GET 请求,这会返回一个文件。

我现在使用此代码(简化)将文件存储到光盘:

public async Task<bool> DownloadFile()
{
    var client = new HttpClient();
    var uri = new Uri("http://somedomain.com/path");
    var response = await client.GetAsync(uri);

    if (response.IsSuccessStatusCode)
    {
        var fileName = response.Content.Headers.ContentDisposition.FileName;
        using (var fs = new FileStream(@"C:\test\" + fileName, FileMode.Create, FileAccess.Write, FileShare.None))
        {
            await response.Content.CopyToAsync(fs);
            return true;
        }
    }

    return false;
}

现在,当此代码运行时,进程会将所有文件加载到内存中。我实际上更希望流从HttpResponseMessage.Content 流式传输到FileStream,因此只有一小部分保存在内存中。

我们计划在大文件(> 1GB)上使用它,那么有没有办法在不将所有文件都放在内存中的情况下实现它?

理想情况下,无需手动循环将一部分读取到byte[] 并将该部分写入文件流直到所有内容都被写入?

【问题讨论】:

  • CopyToAsync 已经在执行您所描述的操作(在内部,它会重复从响应中读取一大块数据并将其写入文件,直到传输所有数据)它不应导致缓冲整个文件立即记忆。
  • 我也是这么想的,但是看看内存消耗,它确实会将整个文件加载到内存中,我想避免这种情况。
  • 你试过response.Content.ReadAsStreamAsync()和使用Stream.CopyToAsync吗?
  • 那么可能是您对“内存消耗”的测量不准确。除非需要,否则运行时不一定会释放“空闲”内存,因此内存使用值在例如任务管理器可能无法反映应用程序实际“使用”的内存。

标签: c# stream asp.net-web-api2


【解决方案1】:

使用带有HttpCompletionOption.ResponseHeadersRead 值的GetAsync(Uri, HttpCompletionOption) 重载代替GetAsync(Uri)

同样适用于SendAsyncHttpClient的其他方法

来源:

在读取部分或所有响应(包括内容)之后,返回的Task对象将根据completionOption参数完成。

【讨论】:

    【解决方案2】:

    看起来这是设计使然 - 如果您查看 HttpClient.GetAsync() 的文档,您会看到它说:

    返回的任务对象将在整个响应后完成 (包括内容)被读取

    您可以改用HttpClient.GetStreamAsync(),其中明确指出:

    此方法不缓冲流。

    但是,据我所知,您然后可以访问响应中的标头。由于这可能是一个要求(因为您从标题中获取文件名),那么您可能想要使用 HttpWebRequest 代替它允许您获取响应详细信息(标题等)而无需将整个响应读入内存.比如:

    public async Task<bool> DownloadFile()
    {
        var uri = new Uri("http://somedomain.com/path");
        var request = WebRequest.CreateHttp(uri);
        var response = await request.GetResponseAsync();
    
        ContentDispositionHeaderValue contentDisposition;
        var fileName = ContentDispositionHeaderValue.TryParse(response.Headers["Content-Disposition"], out contentDisposition)
            ? contentDisposition.FileName
            : "noname.dat";
        using (var fs = new FileStream(@"C:\test\" + fileName, FileMode.Create, FileAccess.Write, FileShare.None))
        {
            await response.GetResponseStream().CopyToAsync(fs);
        }
    
        return true
    }
    

    请注意,如果请求返回不成功的响应代码,则会引发异常,因此您可能希望包装在 try..catch 中并在这种情况下返回 false,就像您原来的示例一样。

    【讨论】:

    • 感谢您的澄清。实际上,在客户端上使用 GetStreamAsync 可以将流直接保存到磁盘而不是内存。由于我们也可以控制服务器,因此我可以先在单独的请求中简单地获取元数据(文件名、大小)。然后我就不需要访问标题了。
    猜你喜欢
    • 2015-06-20
    • 2016-08-07
    • 2015-07-13
    • 2014-03-03
    • 2013-11-07
    • 2013-04-24
    • 1970-01-01
    • 2022-10-13
    • 2016-07-26
    相关资源
    最近更新 更多