求助:.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 条都返回给前台。
4265 次点击
所在节点    .NET
77 条回复
ly841000
2021-11-16 17:12:38 +08:00
@Buges 需要到处插桩的 goroutine 实现好。。。。。。。。。?
dawnh
2021-11-16 17:15:52 +08:00
@dawnh 接这条。
按印象里的搜了一下 SO 找到以前看过的帖子了,随后指向到这个 issue: https://github.com/dotnet/efcore/issues/18571
简单来说,如果你是用了 ToListAsync(),使用 SqlClient ,同时表结构里存在 VARCHAR(MAX)这样的变长列,就会碰到这个性能问题,好多年还没修,因为要修复就牵扯到非常底层的库。引用 issue 里的一条回复:
Workarounds: Don't use VARCHAR(MAX) or don't use async queries.

希望对你有帮助吧。
Buges
2021-11-16 17:19:16 +08:00
@ly841000 color blind function,不用担心 blocking ,插桩用户无感知,不需要用户到处插入 async/await ,你到说说哪里不好?
liuhan907
2021-11-16 19:09:04 +08:00
@Buges 就单凭一条,你不能控制协程调度,这点就很不好了。
Buges
2021-11-16 19:41:18 +08:00
@liuhan907 在 gc 语言中你真的需要控制 suspend point 吗?
liuhan907
2021-11-16 19:51:50 +08:00
@Buges 控制调度和 GC 有什么关系?控制调度的主要目的是为了控制执行上下文,以达到一些特殊的需求。例如复数的独立单线程执行环境,或者并发不并行的执行环境等。
Buges
2021-11-16 20:12:19 +08:00
@liuhan907 因为用 gc 语言一般不追求 precise control ,也就没有必要显式标识 suspend point 。

goroutine 确实不能为特定的 task 专门配置 executor ,但我觉得这是个 structural 的问题,即 spawn 一个 task 之后无法追踪该 task 后续 spawn 的其他 task 。与隐式插入几个 suspend point 关系不大。如果要防止调用某些系统 API 的时候 goroutine 被 move 的话 runtime.LockOSThread 就可以,能说说有什么场景 gc 语言需要关心 task 具体怎样调度么?
liuhan907
2021-11-16 20:26:18 +08:00
@Buges 所以我都说了这个和 GC 根本一点关系都没有,自定义调度器的目的是为了控制并行和并发。一个最简单的例子,我需要不定数量的组,每组的协程只并发不并行,但是不同组的协程可以并行。同时我希望一些组的协程执行可以重入,另一些不能重入。Go 里你无法在不使用任何包装的情况下做到,而使用包装就会大幅降低性能同时代码难看难写。
libasten
2021-11-16 20:37:43 +08:00
@dawnh 变长的列改成固定长度的,会不会好点呢?
Buges
2021-11-16 21:01:22 +08:00
@liuhan907 gc 语言的使用场景下一般不需要关心这种问题,尤其 go 这个号称“大道至简”的 opinionated 语言,更是几乎不在意任何 corner case 。不能为特定 task 自定义调度器是 go 的并发模型缺乏 structural 的问题,而不是 green thread 隐式插入 suspend point 本身的问题。如果支持 scoped thread/task 的话,理论上也是可以实现的。我只想到了 FFI 需要防止 task 被 move 到其他 thread (并且与 ffi 的交互也应该被包装而不是影响到上层的行为),除此之外这应该属于运行时提供的抽象层,如果正常写业务的时候需要关心那这个抽象就是 leaky 的。
所以我想知道,有什么具体的,gc 语言的使用场景下,无法包装且需要对某些特定的 task“只并发不并行”的需求呢?
shiweiliang
2021-11-16 21:03:16 +08:00
这和 controller 生成的方法有什么关系,慢你看一下日志不就行了吗,框架路由执行执行到结束 ,ef core 查询日志,都有精确的时间。
leeg810312
2021-11-16 21:30:12 +08:00
@Buges await/async 是业内公认最佳异步编程模型,新老语言 js Python swift rust 都实现了这个语言特性,它只是编程模型,便利异步开发,难用从何说起?它并不改变开发平台本身的控制调度,你要想自己控制调度不用它就好了。你比较开发平台间异步编程的相同不同就算了,还把这种比较和 await/async 编程模型好不好用联系起来,不仅基本概念错误,比较本身也值得商榷,完全就是伪命题
Buges
2021-11-16 22:07:42 +08:00
@leeg810312 没有银弹,不同模型都有自己的优劣,没有哪个是“公认最佳”。
我一直说的都是“异步编程模型”,以 goroutine/fiber 为代表的 green thread ,以 erlang 为代表的 actor model ,以 trio 和 kotlin coroutine 为代表的 structural concurrency ,以及经典的 async/await 。
说它难用主要体现在两个方面:
1. colored function 。几乎所有的库 /函数都要实现两遍,sync 和 async 的版本。
2. blocking 。async context 里调用同步操作 block 整个 runtime 。
当然还有代码里到处都是 async/await 关键字,不过这个属于个人偏好。
你要说不同平台的具体实现,还有 future based (do nothing until awaited) 和 task based (spawned after creating)的区别。无运行时的语言没法使用 green thread ,有 gc 的语言(尤其 Python 的 asyncio 选择前者)用 async/await 并不能说是良好的选择。js 因为最开始就是异步单线程降低了复杂度同时也确实优于回调模式。Rust 的实现还相当不完善,实现自己的 future 还需要 unsafe ( pin projection)。Swift 不了解就不评价了。
userforg2021
2021-11-16 22:55:20 +08:00
如果.net 是谷歌出的,那应该会更好用
fanshaohua
2021-11-16 23:50:24 +08:00
@userforg2021 不知道你看过 EF core 的文档么?都在 github 上,通过开源社区来维护的.
对比 Facebook 和 Google 的文档,微软强太多了...
扯远了...
https://docs.microsoft.com/en-us/ef/core/

@libasten 还是先看看 EF core 打印出来的日志,确认一下是不是查询慢。
leeg810312
2021-11-17 00:07:24 +08:00
@Buges 大多数开发平台都是同步异步都有,当然要实现 2 种,否则怎么区分,这也能算缺点?异步上下文调用同步方法会阻塞和 async/await 有什么关系?前面我强调了,async await 是用来简化异步编程模型,使源代码的逻辑简明清晰,易于阅读和维护,并没有改变开发平台实现异步编程的控制调度,你非要把编程模型随意扩大解释,把语言特性和背后的实现混为一谈。各个开发平台实现的效果各有好坏,但同步异步都有的开发平台里,async/await 显然是最好的异步编程模型,否则不会在 C#首创后被多个新旧语言学了去。你只是看着好像知道很多知识点,但实际连基本概念都搞不清,只会翻来覆去扯几个名词。
Buges
2021-11-17 01:24:27 +08:00
@leeg810312
比如我要写一个 redis client ,需要用同步 IO 的 API 和异步 IO 的 API 分别实现一遍,代码逻辑完全一样,为什么不能只写一遍?除了底层语言没有必要同步异步都有,像 go 、erlang 可以全都是异步,异步不增加任何额外的复杂度和学习成本。green thread 也没有这个问题。
至于真正需要全都有的,可以了解一下 zig 的 color blind async ,只需要实现一遍,同步 context 下 async 自动变成 noop 。

异步 context 调用同步方法导致阻塞就是 async/await 模型的缺陷。你要说是实现的问题,你到说说哪个 async/await 的实现可以调用同步方法而不阻塞?

贴里讨论的一直都是 programming pattern ,根本没有提到过 scheduler/runtime 具体实现的问题,上面讨论的也是 task 具有 structural 的特性(await task will resume after all sub task awaited finished)从而可以更细粒度的控制不同的 task 由不同的 executor (单线程 /多线程)执行。这确实是 async/await 比 goroutine 的优点,但如果 green thread 引入了 structural concurrency 也是一样可以实现的。

async/await 在过去一段时间确实非常流行,但没有任何范式可以适用于所有的需求,没有银弹,自然没有哪个能成为是“最好”。
何况 async/await 有很多问题和批评, 如 https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ ,hn 上每几个月都上一次头条,没看过可以看一下。

建议多了解一些不同的语言和范式,C#是一门优秀的语言,但它也有历史的局限性。很多当时的设计但若干年后发现有问题的(比如 void 不能作为泛型参数)。erlang/elixir 可以写出天然分布式的程序,actor model 广泛在 OOP 中使用,green thread 极大的简化了异步 IO 的复杂度,直接编写同步代码并获得异步 IO 的并发性能提升 nearly for free 。structural concurrency 是 trio 的作者提出的,要求任何函数 spawn 的 task 必须在函数返回前结束,希望像现代编程语言的控制流取代 goto 一样取代当前 spawn 的 task 可以自由运行 for any time ,详细阅读 https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/
go 里的 errgroup 和 rust 的 scoped_thread 都是该范式的体现,并且被 kotlin 作为了官方的协程范式。
ragnaroks
2021-11-17 08:33:54 +08:00
EF 需要 .AsNoTracking() 否则极大影响性能,不过就算跟踪了也不至于才 4000 条数据要 2 分钟,我更倾向于数据库配置有误、数据库机器性能受限、EF 或框架甚至 SqlServer 本身有 BUG ;事实上 dotnet framework 4.5.2 曾出现过严重的方法重载 BUG 。

在 csharp 板块吹 golang 的建议直接屏蔽,连 java 都不如的语言赶紧去 c/cpp 社区吹。
userforg2021
2021-11-17 08:51:48 +08:00
@fanshaohua 命中友军
userforg2021
2021-11-17 09:01:48 +08:00
我觉得某些人吧,不懂就不要乱说。。。
asp.net core 现在默认是没有同步上下文的。。。
"async context 里调用同步操作 block 整个 runtime"...完全看不懂。。。阻塞大概是在当前有同步上下文的情况下,调用 Task 的同步获取结果的方法,而这个 Task 的同步上下文和当前是同一个才会阻塞。。。
而且阻塞也只是当前线程,阻塞整个 runtime ????????????
另外,太长不看,没有任何参考价值

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

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

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

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

© 2021 V2EX