下载时 HttpClient 不写入流
HttpClient not writing to stream while downloading
目前我正在实施一种使用 HttpClient 报告进度的方法,因为我们与 .NET4 WPF 和 Windows 通用应用程序共享代码,所以我们使用来自 NuGet 的 Microsoft HTTP 客户端库。想法是将目标文件流包装在 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));
之后我打开文件流,然后复制内容流。响应正确 headers:
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
。有什么提示我做错了什么吗?
事实上,ReadAsStreamAsync
是 async
,这让人很怀疑。你为什么要异步等待获取流?您应该异步读取它,但您应该立即准备好流本身。
阅读文档使这一点显而易见:
This operation will not block. The returned task object will complete after the whole response (including content) is read.
但是,在读取 headers 之后,您可以使用重载来获得它 return。这仍然意味着您需要等待服务器处理请求,然后才能开始取得进展,但对于下载本身,您很幸运。
示例代码:
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;
}
目前我正在实施一种使用 HttpClient 报告进度的方法,因为我们与 .NET4 WPF 和 Windows 通用应用程序共享代码,所以我们使用来自 NuGet 的 Microsoft HTTP 客户端库。想法是将目标文件流包装在 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));
之后我打开文件流,然后复制内容流。响应正确 headers:
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
。有什么提示我做错了什么吗?
事实上,ReadAsStreamAsync
是 async
,这让人很怀疑。你为什么要异步等待获取流?您应该异步读取它,但您应该立即准备好流本身。
阅读文档使这一点显而易见:
This operation will not block. The returned task object will complete after the whole response (including content) is read.
但是,在读取 headers 之后,您可以使用重载来获得它 return。这仍然意味着您需要等待服务器处理请求,然后才能开始取得进展,但对于下载本身,您很幸运。
示例代码:
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;
}