.net - .Net HttpClient.GetStreamAsync() 的行为与 .GetAsync() 不同
问题描述
我一直在尝试解决在我的应用程序中下载一批图像时遇到的问题。
如果我HttpClient.GetStreamAsync(url)
用来下载批处理,那么似乎某些请求会超时,并最终出错。
但是,如果我使用HttpClient.GetAsync(url)
,那么整个批次将毫无问题地下载。
我怀疑它与调用时未释放端口有关.GetStreamAsync(url)
,但是我可能在胡说八道。
下面是演示该问题的代码片段。
async Task Main()
{
HttpClient httpclient = new HttpClient();
var imageUrl = "https://tenlives.com.au/wp-content/uploads/2020/09/Found-Kitten-0-8-Weeks-Busy-scaled.jpg";
var downloadTasks = Enumerable.Range(0, 15)
.Select(async u =>
{
try
{
//Option 1) - this will fail
var stream = await httpclient.GetStreamAsync(imageUrl);
//End Option 1)
//Option 2) - this will succeed
//var response = await httpclient.GetAsync(imageUrl);
//response.EnsureSuccessStatusCode();
//var stream = await response.Content.ReadAsStreamAsync();
//End Option 2)
return stream;
}
catch (Exception e)
{
Console.WriteLine($"Error downloading image");
throw;
}
}).ToList();
try
{
await Task.WhenAll(downloadTasks);
}
catch (Exception e)
{
Console.WriteLine("================ Failed to download one or more image " + e.Message);
}
Console.WriteLine($"Successful downloads: {downloadTasks.Where(t => t.Status == TaskStatus.RanToCompletion).Count()}");
}
在 linqSelect
语句的代码块中,Option 1)
将如上所述失败。如果您注释掉 1),并取消注释选项 2),那么一切都会成功。
谁能解释这里可能发生的事情?
编辑:这似乎适用于 .net 核心。我可以使用 .net framework 4.7.2 及更低版本重现此问题
EDIT2:我还观察到,如果我通过添加来增加默认连接限制,
ServicePointManager.DefaultConnectionLimit = 30;
则不再发生错误,但这并不能解释为什么选项 1) 失败但选项 2 成功)
解决方案
正如@RichardDeeming 所解释的HttpClient.GetStreamAsync
,HttpClient.GetAsync
使用HttpCompletionOption.ResponseHeadersRead
.
代码可以改写如下:
async Task Main()
{
HttpClient httpclient = new HttpClient();
var imageUrl = "https://tenlives.com.au/wp-content/uploads/2020/09/Found-Kitten-0-8-Weeks-Busy-scaled.jpg";
var downloadTasks = Enumerable.Range(0, 15)
.Select(async u =>
{
try
{
//Option 1) - this will fail
var response = await httpclient.GetAsync(imageUrl, HttpCompletionOption.ResponseHeadersRead);
//End Option 1)
//Option 2) - this will succeed
//var response = await httpclient.GetAsync(imageUrl, HttpCompletionOption.ResponseContentRead);
//End Option 2)
response.EnsureSuccessStatusCode();
var stream = await response.Content.ReadAsStreamAsync();
return stream;
}
catch (Exception e)
{
Console.WriteLine($"Error downloading image");
throw;
}
}).ToList();
try
{
await Task.WhenAll(downloadTasks);
}
catch (Exception e)
{
Console.WriteLine("================ Failed to download one or more image " + e.Message);
}
Console.WriteLine($"Successful downloads: {downloadTasks.Where(t => t.Status == TaskStatus.RanToCompletion).Count()}");
}
下一个HttpClient.GetAsync
电话HttpClient.SendAsync
。我们可以在GitHub 上查看该方法的代码:
//I removed the uninterested code for the question
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
{
TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>();
client.SendAsync(request, cancellationToken).ContinueWith(task =>
{
HttpResponseMessage response = task.Result;
if(completionOption == HttpCompletionOption.ResponseHeadersRead)
{
tcs.TrySetResult(response);
}
else
{
response.Content.LoadIntoBufferAsync(int.MaxValue).ContinueWith(contentTask =>
{
tcs.TrySetResult(response);
});
}
});
return tcs.Task;
}
使用HttpClient.GetAsync
(或SendAsync(HttpCompletionOption.ResponseContentRead)
),网络缓冲区中的接收内容被读取并集成到本地缓冲区中。
我不确定网络缓冲区,但我认为某处的缓冲区(网卡、操作系统、HttpClient、???)已满并阻止新响应。
您可以通过正确管理此缓冲区来更正代码,例如通过处理关联的流:
var downloadTasks = Enumerable.Range(0, 15)
.Select(async u =>
{
try
{
var stream = await httpclient.GetStreamAsync(imageUrl);
stream.Dispose(); //Free buffer
return stream;
}
catch (Exception e)
{
Console.WriteLine($"Error downloading image");
throw;
}
}).ToList();
在 .Net Core 中,原始代码无需更正即可工作。HttpClient 类已被重写,当然也得到了改进。
推荐阅读
- javascript - Nightwatch JS 中的异步命令执行
- r - 有没有办法创建一个循环来查找数据集中的特定方法?
- python - 烧瓶如何在扩展模板中获取变量
- python - 代码没有读取 Elif 或 Else 语句
- javascript - 静态包含 HTML 代码而不是在实时环境中的方法?
- visual-studio-code - 如果 VS Code 扩展选项卡没有打开,有没有办法打开它?
- python - 将 .TXT 数据作为一列拆分为“n”行,如 Python 中的 CSV?
- c - 编写一个文本文件以在 C 程序中保存向量和其他变量
- javascript - 类组件不等待函数返回
- flutter - 有没有办法为 Flutter StatefulBuilder 设置一个定期计时器,每 1 秒用 setState() 更新一次?