在 ASP.NET Core 中一次生成 100-500 个任务(每个 HTTP 请求)是一种不好的做法吗? [关闭]
Posted
技术标签:
【中文标题】在 ASP.NET Core 中一次生成 100-500 个任务(每个 HTTP 请求)是一种不好的做法吗? [关闭]【英文标题】:Is spawning 100-500 task at once (per HTTP request) a bad practice in ASP.NET Core? [closed] 【发布时间】:2021-12-11 16:15:40 【问题描述】:在我的 ASP.NET Core 控制器中,我有一个如下所示的 Action:
public async Task<ActionResult> SomeAction(int someId)
int[] itemIds = await _service.GetSomeItems(someId);
var model = new List<ItemDetail>(itemIds.Length);
foreach (int id in itemIds)
model.Add(await _service.GetItemDetails(id));
return View(model);
它可以工作,但速度很慢。我想通过生成多个并行任务来提高“SomeAction”的性能:
public async Task<ActionResult> SomeAction(int someId)
int[] itemIds = await _service.GetSomeItems(someId);
var tasks = new List<Task>();
foreach (int id in itemIds)
tasks.Add(_service.GetItemDetails(id));
var model = await Task.WhenAll(tasks);
return View(model);
这种方法效果更好,但我不确定这种方法是否是一种好的做法。通常 _service.GetSomeItems API 返回 20-30 个元素,但有时它可能返回 100-200 个元素。
在我的情况下,_service
是第 3 方 REST API,延迟为 100-150 毫秒。
我的问题是 - 我一次可以生成多少个任务?如果同时出现多个请求(每个请求将产生 20-200 个任务),我的代码会导致 ThreadPool 饥饿吗? 我的方法通常是一种不好的做法吗?
我要避免的是 ThreadPool 饥饿,在某些情况下可能会导致死锁。
更新:
_service.GetItemDetails(id)
- 是第 3 方 Web API,请求延迟为 100-150 毫秒。
【问题讨论】:
如果没有看到内部GetItemDetails
,我们将无法帮助您。
我们无法评论黑匣子的优缺点
另外,您使用的是IHttpClientFactory
吗?或者你是在为每个请求做new HttpClient()
吗?您可能会成为套接字耗尽的受害者 - 如果您正在缓存单个 HttpClient
,那么您将拥有一个陈旧的 DNS 缓存。
一般来说,在网站中,您不想考虑并行运行代码 - 由于站点正在服务的其他请求,您已经对并行活动有大量需求。除非您实际上正在开发专用于单个用户的应用程序。
您可能会发现这很有用:How to limit the amount of concurrent async I/O operations?
【参考方案1】:
这不是一个好习惯。问题不在于可以创建多少任务,而在于其他服务器在阻止或限制您之前将接受多少请求、网络开销和延迟、您的网络上的压力。您正在进行网络调用,这意味着您的 CPU 无需执行任何操作,它只是等待来自远程服务器的响应。
然后是服务器端的开销:每个请求打开一个连接,执行单个查询,序列化结果。如果服务器的代码写得不好,它可能会崩溃。如果负载均衡器配置不当,所有 1000 个并发调用可能最终都由同一台机器提供服务。这 1000 个请求可能会打开 1000 个相互阻塞的连接。
如果远程 API 允许,请尝试一次请求多个项目。您只需支付一次开销,服务器只需执行一次查询。
否则,您可以尝试同时执行多个连接,但不能同时执行所有连接。最好通过限制并发执行的数量以及可能的频率来限制请求。
在 .NET 6(下一个 LTS 版本,已在生产中支持)中,您可以在有限的并行度下使用 Parallel.ForEachAsync
:
ConcurrentQueue<Something> list=new ConcurrentQueue<Something>(1000);
var options=new ParallelOptions MaxDegreeOfParallelism = 20;
await Parallel.ForEachAsync(itemIds,options,async id=>
var result=_service.GetItemDetails(id);
var something=CreateSomething(result);
list.Add(something);
);
model.Items=list;
return View(model);
另一种选择是创建使用 Channels 或 TPL Dataflow 块来创建同时处理项目的处理管道,例如第一步生成 ID,下一步检索远程项目,最后一个产生最终结果:
ChannelReader<int> GetSome(int someId,CancellationToken token=default)
var channel=Channel.CreateUnbounded<int>();
int[] itemIds = await _service.GetSomeItems(someId);
foreach(var id in itemIds)
if(token.IsCancellationRequested)
return;
channel.Writer.TryWrite(id);
return channel;
ChannelReader<Detail> GetDetails(ChannelReader<int> reader,int dop,
CancellationToken token=default)
var channel=Channel.CreateUnbounded<Detail>();
var writer=channel.Writer;
var options=new ParallelOptions
MaxDegreeOfParallelism=dop,
CancellationToken=token
;
var worker=Parallel.ForEachAsync(reader.ReadAllAsync(token),
options,
async id=>
try
var details=_service.GetItemDetails(id);
await writer.WriteAsync(details);
catch(Exception exc)
//Handle the exception so we can keep processing
//other messages
);
worker.ContinueWith(t=>writer.TryComplete(t.Exception));
return channel;
async Task<Model> CreateModel(ChannelReader<Detail> reader,...)
var allDetails=new List<Detail>(1000);
await(foreach var detail in reader.ReadAllAsync(token))
allDetails.Add(detail);
//Do other heavyweight things
var model=new Model Details=allDetails,.....);
这些方法可以链接在管道中:
var chIds=GetSome(123);
var chDetails=GetDetails(chIds,20);
var model=await CreateModel(chDetails);
这是使用 Channels 时的常见模式。在 Go 等语言中,通道用于创建多步处理管道。
将这些方法转换为扩展方法允许以流畅的方式创建管道:
static ChannelReader<Detail> GetDetails(this ChannelReader<int> reader,int dop,CancellationToken token=default)
static async Task<Model> CreateModel(this ChannelReader<Detail> reader,...)
var model= await GetSome(123);
.GetDetails(20)
.CreateModels();
【讨论】:
以上是关于在 ASP.NET Core 中一次生成 100-500 个任务(每个 HTTP 请求)是一种不好的做法吗? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章
ASP.NET Core 1.0 中使用 Swagger 生成文档
[Asp.Net Core]ASP.NET Core WebApi使用Swagger生成api说明文档看这篇就够了
(VIP-朝夕教育)2021-06-19 .NET高级班 56-ASP.NET Core 管道中间件详解
asp.net core web api 生成 swagger 文档