【发布时间】:2018-11-05 14:55:01
【问题描述】:
我计划使用https://github.com/Dasync/AsyncEnumerable 异步逐行读取远程文件(因为还没有异步流[C# 8 可能]:https://github.com/dotnet/csharplang/blob/master/proposals/async-streams.md):
public static class StringExtensions
{
public static AsyncEnumerable<string> ReadLinesAsyncViaHttpClient(this string uri)
{
return new AsyncEnumerable<string>(async yield =>
{
using (var httpClient = new HttpClient())
{
using (var responseStream = await httpClient.GetStreamAsync(uri))
{
using (var streamReader = new StreamReader(responseStream))
{
while(true)
{
var line = await streamReader.ReadLineAsync();
if (line != null)
{
await yield.ReturnAsync(line);
}
else
{
return;
}
}
}
}
}
});
}
public static AsyncEnumerable<string> ReadLinesAsyncViaWebRequest(this string uri)
{
return new AsyncEnumerable<string>(async yield =>
{
var request = WebRequest.Create(uri);
using (var response = request.GetResponse())
{
using (var responseStream = response.GetResponseStream())
{
using (var streamReader = new StreamReader(responseStream))
{
while(true)
{
var line = await streamReader.ReadLineAsync();
if (line != null)
{
await yield.ReturnAsync(line);
}
else
{
return;
}
}
}
}
}
});
}
}
似乎它们都在一个简单的控制台应用程序中运行得很好,如下所示:
public class Program
{
public static async Task Main(string[] args)
{
// Or any other remote file
const string url = @"https://gist.githubusercontent.com/dgrtwo/a30d99baa9b7bfc9f2440b355ddd1f75/raw/700ab5bb0b5f8f5a14377f5103dbe921d4238216/by_tag_year.csv";
await url.ReadLinesAsyncViaWebRequest().ForEachAsync(line =>
{
Console.WriteLine(line, Color.GreenYellow);
});
await url.ReadLinesAsyncViaHttpClient().ForEachAsync(line =>
{
Console.WriteLine(line, Color.Purple);
});
}
}
...但是如果将它用作 ASP.NET Core WebAPI 的一部分来处理这些行,然后使用 PushStreamContent 推送它们,我会有些担心:
- https://docs.microsoft.com/en-us/previous-versions/aspnet/hh995285(v=vs.108)
- https://blog.stephencleary.com/2016/10/async-pushstreamcontent.html
我们的想法是拥有一个利用 async / await 的数据管道,以便使用的线程数尽可能少,同时避免内存增加(利用类似枚举的AsyncEnumerable 的特性)。
我阅读了几篇文章,但似乎都是非 .NET Core 版本,我真的不知道在我想要实现的目标方面是否存在一些潜在的性能问题/警告?
- Difference between HttpRequest, HttpWebRequest and WebRequest
- http://www.diogonunes.com/blog/webclient-vs-httpclient-vs-httpwebrequest/
“商业”案例的一个例子是:
using System;
using System.Collections.Async;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace WebApplicationTest.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class DumbValuesController : ControllerBase
{
private static readonly Random Random = new Random();
// GET api/values
[HttpGet]
public async Task<IActionResult> DumbGetAsync([FromQuery] string fileUri)
{
using (var streamWriter = new StreamWriter(HttpContext.Response.Body))
{
await fileUri.ReadLinesAsyncViaHttpClient().ForEachAsync(async line =>
{
// Some dumb process on each (maybe big line)
line += Random.Next(0, 100 + 1);
await streamWriter.WriteLineAsync(line);
});
}
return Ok();
}
}
}
【问题讨论】:
-
始终使用
HttpClient。这是标准。其他一切都只是为了向后兼容。 -
@ChrisPratt 我在想流的类型(底层实现)可能在两者之间有所不同。
-
@EhouarnPerret 您将流与可枚举混淆。我怀疑有一些 Java 根源。流与 IO 相关,而不是枚举,并且它们 是异步的,因为 .NET 1.0 早在 2002 年。 HttpWebRequest 也是异步的,但 HttpClient 更好,因为它不必执行 DNS 解析和 HTTPS 握手每次通话。在 .NET Core 2+ 中,它也使用更新、更快的 Sockets 实现。结合HttpClientFactory和Polly,提供HTTP连接池、重试策略等
-
@EhouarnPerret 你到底想做什么?除非 HTTP 请求返回一个文本文件,否则您无法逐行读取它。如果您想在结果到达后立即发布,您需要一种发布/订阅机制,例如 System.Threading.Channels 提供的机制。如果要处理原始数据,那就是 System.IO.Pipelines,它在顶部添加了内存管理和最小分配
-
@PanagiotisKanavos 没有任何 Java 根,但我真的很喜欢你带来的细节,非常感谢。不知道 Polly :) 我将编辑我的问题并添加我的业务用例。旁注,如果您查看 StreamReader,它会利用底层流并读取数据块(缓冲区),直到到达 eol 分隔符。
标签: c# asp.net-core .net-core dotnet-httpclient webrequest