求助:.NET Core WebAPI 框架自动生成的 Controller 中的 get 全部对象的方法执行太慢

2021-11-16 10:48:45 +08:00
 libasten
框架自动生成的代码:

```
[HttpGet]
public async Task<ActionResult<IEnumerable<DocItem>>> GetDocItem()
{
return await _context.DocItem.ToListAsync();
}
```

数据库中 4000 条数据,执行这个 api 卡顿 2 分钟都没有反应。
不要后台分页的建议,因为需求是想一次性把这 4000 条都返回给前台。
4282 次点击
所在节点    .NET
77 条回复
encro
2021-11-17 09:49:04 +08:00
简单的定位方法:

1 ,打开数据库,show processlist ,看看 db 语句的运行状态,是不是在 send data 。如果是那么就是数据库到 web 服务器慢,或者数据集大。(带宽,走了外网)
2 ,如果 sql 数据库没问题,那么可能是转对象 json 比较大,对象转 json 比较慢(可以缓存下再测试);
3 ,如果只查 100 条,还是那么慢吗?
4 ,是不是时间花在 dnslookup 之类的了?
libasten
2021-11-17 10:11:04 +08:00
@quan01994 #12


@fanshaohua #35

打印了 sql ,确实不是查询卡的,查询 2ms 就执行完了,在这个 api 的 return 语句执行中,回去遍历模型结构导致的?因为的 F10 看了,一直在循环执行模型属性中的 get 方法。

我改了一下代码,用一个 list 对象暂存查询返回对象,获取列表对象的语句瞬间完成,但是下一句 return 语句是会卡住的,请问这个问题怎么排查呢?

```
[HttpGet]
public async Task<ActionResult<IEnumerable<ZLJBXXItem>>> GetZLJBXXItem()
{
List<ZLJBXXItem> lst = await _context.ZLJBXXItem.ToListAsync(); //这句瞬间返回 lst 值
return lst; // 卡顿在这句
}
```
poorcai
2021-11-17 10:20:08 +08:00
楼主是不是做医疗的?不会是杭州的吧?
quan01994
2021-11-17 10:37:13 +08:00
@libasten IEnumerable 改成 list 试试看,你这样返回的是一个可以迭代的对象,
Itoktsnhc
2021-11-17 10:38:24 +08:00
感觉是 ActionResult 的问题?

``` c#
[HttpGet(Name = "GetWeatherForecast")]
public async Task<ActionResult<IEnumerable<WeatherForecast>>> Get()
{
await Task.CompletedTask;
var list = Enumerable.Range(1, 1000).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
}).ToList();
return list;
}
```
这样的代码也会卡一会
Itoktsnhc
2021-11-17 10:39:55 +08:00
sorry 提供错了
quan01994
2021-11-17 10:44:23 +08:00
你这样返回只是为了 swagger 可以找到返回类型,可以展示出来
我一般都是这样写的

[HttpGet("List")]
[ProducesResponseType(typeof(List<string>),StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> List()
{
var list = new List<string>();
return Ok(list);
}


你可以参考一下 。
Itoktsnhc
2021-11-17 10:50:04 +08:00
Swagger 的两分钟有没有可能是 Swaggerui 渲染比较慢? 实际的网络请求也同样的慢吗?
quan01994
2021-11-17 10:50:37 +08:00
JSON 序列化方法,默认用的是微软官方的 。虽然按照微软官方的文档,序列化确实比 Newtonsoft.Json 快,但是不排除你有什么特殊的地方, 所以,替换一下默认 Json 序列化方法 用 Newtonsoft.Json 试试看 。
0o0o0o0
2021-11-17 11:47:23 +08:00
“block 整个 runtime”
我的理解是 async 其实只是 task 的语法糖,最终还是会追溯到一个线程。
用 await 的话就是把异步变成同步的,并且不会导致因为等待线程执行而阻塞当前线程(等待时会释放 cpu 资源)。
所以说最后不应该是阻塞当前的线程吗,而且同步方法本身就应该阻塞线程啊。。。
liuhan907
2021-11-17 11:48:38 +08:00
@Buges 为啥你非要往 GC 上拐呢,自定义调度和 GC 到底有什么关系。你要场景,那一个很简单的,房间类游戏,我希望房间内的请求都是单线程处理,但是房间内在处理耗时的异步操作的时候,我任然希望这个房间能处理只读请求。这不就是并发不并行,允许重入。其它的场景挺多的,再比方说 actor 模型那就是典型的这个样的场景。
leeg810312
2021-11-17 12:24:10 +08:00
@Buges erlang go 都是面向并发的编程语言,和同步异步都有的开发平台比较,你就像在拿 OLTP 和 OLAP 比较,说 OLTP 为什么没有 OLAP 读性能好。

关于异步同步阻塞,只说.net ,以前的 .net 版本有同步上下文设计,但 .net core 以后的版本就不是这个设计了,前面有人说了,异步任务调用同步方法只阻塞当前任务,哪有阻塞整个应用的,而且实现变了 async/await 编程模型却没有变。即使其他开发平台有你说的这种情况,也正说明和实现有关,和 async/await 没有关系。

另外,你认可的 Kotlin 也有 async/await ,但不影响它有不同的调度器,并且不同的编译目标还用不同的实现。明显,好坏不谈,async/await 编程模型可以有各种实现,但你始终模糊 编程模型和实现 的界限,将它们混在一起。

建议教育别人前先自己厘清概念,还有你的回复中那么多名词不是专有词汇都是有中文翻译的,我和香港客户谈技术问题都不会夹这么多英文词汇,是不是你中文不太好?
0o0o0o0
2021-11-17 12:41:14 +08:00
@0o0o0o0 看了一下资料,原来是在一个只能单线程的上下文中,以阻塞的方式调用异步方法,会导致异步方法中的线程结束后无法返回结果,因为返回结果需要当前的上下文继续运行,而上下文又因为等待结果被阻塞了,从而陷入死锁。如果上下文允许多线程则不会发生这个事情,所以说最好就是不要使用阻塞方式调用异步方法。
hnbcinfo
2021-11-17 13:12:19 +08:00
我怀疑你实体里有循环引用。不要返回整个实体,试试用 DTO ,只返回用到的数据。
neilq
2021-11-17 13:50:18 +08:00
@userforg2021 他们写的时候一定觉得自己知识很渊博
forgottencoast
2021-11-17 14:48:20 +08:00
@leeg810312 我想给你 1000000 个感谢,强行夹英文的中文看的想吐,有本事全用英文啊,这里的人又不是看不懂。
Buges
2021-11-17 14:56:41 +08:00
@liuhan907 我说 gc 是因为 gc 语言的应用场景主要是写业务和应用,只要在该执行的时候执行就好,不关心具体如何调度。同时 gc 语言也不在意一些小小的 overhead ,所以可能并不值得 async/await 造成的过度的复杂度。我倒是不了解确实有这种需求,所以请教一下你这个例子中如果并行了会怎样?或者说为什么不能简单的用 task 处理所有的任务?
ly841000
2021-11-17 15:06:55 +08:00
@leeg810312 go 太多狂教徒,和普通人不在一个频道,就喜欢扯一些专有名词左顾右盼,不回答具体问题,基本没有交流价值
Buges
2021-11-17 15:10:39 +08:00
@leeg810312 同步异步都有不外乎:
1. 该平台早期就没有异步,异步是后来加的。
2. 异步有较大的复杂度,不值得总是使用。
3. 底层无运行时的语言,当然需要都支持。

async/await 无论哪个实现形式都是一样的:函数转换成一个状态机,内部的 await 作为显式的 suspend point ,当执行到的时候将函数挂起,状态保存,然后控制流返回给 runtime 。runtime 在某个时间点再次 schedule 该 task ,直到结束。
异步 context 中调用阻塞操作会使 runtime 无法拿回控制流,整个线程就被阻塞了,而不单单是一个 task 。而这个线程是 runtime 的 worker 线程。如果 runtime 有 10 个 worker 线程并发处理一万个 task ,那么阻塞一个就只剩 9 个线程可以执行其他 task 了,并且由于 task 都是写并发的,代码里有阻塞部分大概率不止会阻塞一个线程,这样就会导致整个系统的吞吐量大幅下降,也就是阻塞整个 runtime 。

kotlin 则是完全不同的模式,虽然和 async/await 有一定的相似性,但这和调度器有啥关系?
ly841000
2021-11-17 15:19:47 +08:00
@Itoktsnhc 你可以考虑下系统防火墙之类原因,GetWeatherForecast 这段代码刚我测试了下返回是很快的,没有问题

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/815708

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX