HttpClient在下载时不写入流

时间:2015-03-12 16:24:32

标签: c# http stream win-universal-app dotnet-httpclient

目前我正在实现一种报告HttpClient进度的方法,因为我们使用来自NuGet的Microsoft HTTP客户端库与.NET4 WPF和Windows通用应用程序共享代码。我们的想法是将目标文件流包装在CountingInputStream中并在那里报告进度:

 public override void Write(byte[] buffer, int offset, int count)
    {
        _stream.Write(buffer, offset, count);

        _bytesRead += count;
        _progress.Report(_bytesRead);

        if (_cancellationToken.IsCancellationRequested)
        {
            _cancellationToken.ThrowIfCancellationRequested();
        }
    }

然后我发送请求:HttpResponseMessage httpResponseMessage = AsyncHelpers.RunSync(() => _httpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken));

之后,我打开文件流,然后复制内容流。响应具有正确的标头: Content-Length: 213334 Content-Type: application/octet-stream; charset=UTF-8 Content-Disposition: attachment; filename="Bondi Beach.jpg"; filename*=UTF-8''Bondi%20Beach.jpg

using(Stream fileStream = new CountingInputStream(storage.Open(downloadRequest.TargetPath, FileMode.Create), downloadRequest.Progress, cancellationToken )) {                                               
                await HttpHeaderResponseMessage.Content.CopyToAsync(fileStream);
       }

问题是StreamContent仅在下载完成后才开始写入文件流。当它开始编写进度报告时工作正常。

我已经尝试过不同的方法:

  • ReadAsStreamAsync然后将响应流复制到文件流
  • ReadAsStreamAsync手动读取缓冲区然后写入文件流
  • _httpClient = new System.Net.Http.HttpClient(){MaxResponseContentBufferSize = 4096};限制BufferSize

我是否可以强制ContentStream在文件流仍在下载时强制写入文件流?

更新: 根据Luaans的建议,我试图覆盖WriteAsync并实现了StreamContent Extensions方法:

//CountingInputStream
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
    {
        _bytesRead += count;
        _progress.Report(_bytesRead);

        if (_cancellationToken.IsCancellationRequested)
        {
            _cancellationToken.ThrowIfCancellationRequested();
        }
        return _stream.WriteAsync(buffer, offset, count, cancellationToken);
    }
//static Extensions Class
public static async Task CopyToAs(this StreamContent source, Stream targetStream)
    {
        int read;
        byte[] buffer = new byte[4096];
        using(Stream responseStream = await source.ReadAsStreamAsync()) {
            while ((read = await responseStream.ReadAsync(buffer,0,buffer.Length))>0) {
                await targetStream.WriteAsync(buffer, 0, read);
            }
        }
    }

它仍然会等到下载完成,直到它第一次调用ReadAsync。什么提示我做错了什么?

1 个答案:

答案 0 :(得分:4)

事实ReadAsStreamAsync是,async,这使得这一点更加可疑。为什么异步等待获取?你应该异步读取它,但是你应该立即准备好流。

阅读文档会使这显而易见:

  

此操作不会阻止。在读取完整个响应(包括内容)后,返回的任务对象将完成。

但是,在读取标题之后,可以使用重载来使其返回。这仍然意味着您需要等待服务器处理请求,然后才开始取得进展,但对于下载本身,您很幸运。

示例代码:

var response = 
 await 
 (
   new HttpClient()
   .GetAsync("http://www.microsoft.com/", HttpCompletionOption.ResponseHeadersRead)
 );

var stream = await response.Content.ReadAsStreamAsync();
var buffer = new byte[2048];    

while (await stream.ReadAsync(buffer, 0, buffer.Length) > 0)
{
  // Report progress and write to a different stream
}

修改

听起来你应该使用Windows.Web.Http.HttpClient代替System.Net.Http.HttpClient

async Task DownloadWithProgress()
{
 var awaitable = httpClient.GetAsync(yourUrl)

 awaitable.Progress = (res, progress) =>
 {
   // Report progress
 }

 await awaitable;   
}