V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
daijinming
V2EX  ›  程序员

dotnet 开发 API 的时候使用 asyn 、await 有什么优势吗

  •  
  •   daijinming · 2019-11-06 15:44:22 +08:00 · 3920 次点击
    这是一个创建于 1840 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如果一个 API 被标记为 asyn,和 没有 asyn 的 api 有什么区别?不是太理解,这个和多线程有关系吗,求教

    29 条回复    2019-11-08 15:05:31 +08:00
    darkalien
        1
    darkalien  
       2019-11-06 15:54:44 +08:00
    darkalien
        2
    darkalien  
       2019-11-06 16:02:27 +08:00
    关于 async await 的理解,另推荐自己的文章:异步编程关键字 Async 和 Await - https://www.cnblogs.com/AlienXu/p/9529541.html
    hihipp
        3
    hihipp  
       2019-11-06 16:16:10 +08:00
    服务器上的 async 有一篇官方文章说过,很久了,知识还是可用的,是从线程池说起的。

    英文原文: https://msdn.microsoft.com/en-us/magazine/dn802603.aspx?f=255&MSPPError=-2147217396
    中文: https://msdn.microsoft.com/zh-cn/magazine/dn802603.aspx?f=255&MSPPError=-2147217396
    loqixh
        4
    loqixh  
       2019-11-06 16:27:04 +08:00
    await 本质就是通过编译器变化的无栈协程, 和 go 的协程是一样的, 其它的说法都是往复杂里扯
    secondwtq
        5
    secondwtq  
       2019-11-06 19:05:33 +08:00
    @loqixh Go 是 ”stackful“ 的吧 ...
    ”stackless“ 的有 JavaScript,C++,Kotlin,一大堆 ...
    loqixh
        6
    loqixh  
       2019-11-06 19:11:18 +08:00
    @secondwtq 对 但是效果一样, 只是实现方法的区别
    tinkerer
        7
    tinkerer  
       2019-11-06 19:22:51 +08:00
    tinkerer
        8
    tinkerer  
       2019-11-06 19:23:54 +08:00
    sorry, 误操作, 十分抱歉, 上一条与此贴无关
    seakingii
        9
    seakingii  
       2019-11-06 21:28:14 +08:00   ❤️ 1
    你可以理解为在 await 的地方中断执行,在其它线程执行,执行完成后再返回 await 处,继续执行后面的代码.

    和没有 async,await 的版本相比,就是提高了系统的并发量
    seakingii
        10
    seakingii  
       2019-11-06 21:29:30 +08:00
    另外以前是没有 async,await 机制的,要写类似的代码,需要自己写线程,或者利用线程池什么的.代码和现在比起来复杂的多.
    Presbyter
        11
    Presbyter  
       2019-11-06 21:47:47 +08:00
    编译器的语法糖,可以更方便的使用线程池进行多线程应用开发.
    MonoLogueChi
        12
    MonoLogueChi  
       2019-11-06 23:51:20 +08:00 via Android   ❤️ 1
    这个是异步编程,可以减少阻塞。另外 async 只是标记这个方法里可能会出现 await,在编译时没有实际作用,你也可以标记 async 而不写 await,这样编译也能过,只是 IDE 会提示你不要这样写。
    lbp0200
        13
    lbp0200  
       2019-11-07 00:21:35 +08:00 via iPhone
    关键字放在这里,让人感觉怪怪的
    luozic
        14
    luozic  
       2019-11-07 02:45:34 +08:00 via iPhone
    核心是现在大部分的应用是 IO 密集,实际就是 curd 密集型应用;
    xuanbg
        15
    xuanbg  
       2019-11-07 09:11:23 +08:00
    异步模式的好处就是可以同时进行多项任务,减少任务的总时间。就像做饭一样,没人会等电饭煲做好饭再开始炒菜。都是做饭(async)、炒菜(async)并行的,最后饭和菜都好了(await),就能吃了。
    xingheng
        16
    xingheng  
       2019-11-07 11:12:51 +08:00
    @xuanbg 这个🌰好。但是如果有客人(request)就点了一个蛋炒饭(async),这样的话我觉得 sync 版的蛋烧饭会比 async 版的蛋炒饭更快,因为没有切换线程的损耗了。

    关于楼上提到的并发量提高的问题我有些质疑,一个 request 在发起到完成之前,这个 working thread 应该是一直存在的,并不会因为 async 的使用导致 working thread dispose 或者 reuse 的情况。

    我的理解对吗?
    crclz
        17
    crclz  
       2019-11-07 12:57:05 +08:00
    @xingheng 这个 working thread 不能说时一直存在的。
    假如我调用 await GetDishAsync(),那么 GetDishAsync 任务所用到的线程会被高效管理。
    好了,现在问题就来了:GetDishAsync 的 caller 所在的线程(你提到的 working thread )是否会一直存在?
    不是的。因为 await GetDishAsync()的 caller 一定是一个 async 方法,它的线程资源一样会被高效管理。
    xingheng
        18
    xingheng  
       2019-11-07 13:30:50 +08:00
    @crclz 有理。那我们再往下(main thread)追溯,(我没有写过 c#服务端,从其他语言推断的),main thread 或者说函数入口一定是 sync,每一个从 port 过来的请求一定是在 c#服务端对应一个 working thread 的,高效管理是语言级的特性,但是 working thread 的数量一定是不会减少的,working thread 之间(对外)也没有依赖关系,所以并发量还是没有增大。

    我上面的蛋炒饭(async)假定确实很 critical,全程只有一个 async task 的需求是非常少见的,async 在这种情况下应该是没有什么好处的,高效管理只是说损耗很小,但不是没有。

    async 在设计层面上是成功的,也推荐使用。在 API 设计层面上我觉得只是相对提高了响应请求处理的单位时间,勉强上可以说是间接地提高了并发量。

    请指正。
    xuanbg
        19
    xuanbg  
       2019-11-07 14:19:40 +08:00
    @xingheng 并发量是会提高的。系统在同一时间能处理的 request 数量是和 CPU 的核数是一致的。那么,每个 request 的处理时间越短,“单位时间”内能处理的 request 数量就越多。请注意,我们正常情况下说的并发量,都是指「单位时间内的 request 处理数量」。
    xuanbg
        20
    xuanbg  
       2019-11-07 14:26:25 +08:00
    @xingheng 上面的说法可能还不是太准确。事实上,在 CPU 满负荷的情况下,异步并不一定能够提高效率。甚至有可能因为线程切换而导致效率下降。而正常情况下,CPU 负荷是需要控制在 60%以下的。这个时候,异步基本上都能提升处理效率。当然,CPU 的也负载会比同步更高一些。
    crclz
        21
    crclz  
       2019-11-08 00:52:03 +08:00
    回复测试
    crclz
        22
    crclz  
       2019-11-08 00:59:31 +08:00
    @xingheng 同步方法可以直接执行 DoSomethingAsync(),不用 await。所以就只会存在一个线程(或者几个)不断地接受请求、将请求传至 pipeline,经过各种中间件。从第一个中间件开始,就已经是 async 方法。另外我推测应该是直接调用的 DoSomethingAsync(),不加 await。我再在下文说一下何为“高效管理”。

    @xuanbg 并发量和 cpu 核心数的关系粗略的来说是呈线性关系,但具体的下文讲。

    先从数据库连接池的最佳大小说起。
    连接池的最佳大小:connections = (core_count * 2) + effective_spindle_count。core_count:核心数; effective_spindle_count=貌似是有效磁盘数量。很久以前读的文章,记不太清。
    https🐎github.com🐎brettwooldridge/HikariCP/wiki/About-Pool-Sizing
    crclz
        23
    crclz  
       2019-11-08 00:59:44 +08:00
    这篇文章中貌似还说,其实所有类似的(例如线程池,选择最佳的线程池大小),也基本适用于这个公式。
    也就是说,线程池的大小有一个最优的大小。在这个最优的大小下,你的吞吐量、响应时间最优。

    这个最优的大小由 C#管理,程序员不用操心。

    而 C#的 Task 是什么? Task 可以简单的理解为“任务”。你不用关心这些“任务”是如何完成的,你只需要知道:可以用 DoSomethingAsync()来开启一个 Task (并获得该 Task 实例)、可以用 await SomeTask 来等待 Task 结束。至于如何完成 Task 的任务,则是 c#的事情——c#会高效利用线程池里的资源,来完成“任务”:在 await 一个阻塞性的 task 时,没有线程会被拿来等待(这也是传统多线程方式无法企及的)。一个 Task 可能会先后被 1 个、2 个、5 个或者多个 worker thread 接手,但这些东西开发者都不需要关心。又由于线程池里 worker thread 的数量设计很合理,所以这种方法能达到最优的表现。

    唯一的缺点就是,要严格控制代码(或许 VS 的各种 analyzer 包能容易的做到这一点),不能出现传统的同步阻塞式方法 例如 File.Read();而是要将它们全都换成 await File.ReadAsync()这种。因为线程池的 worker thread 资源是很宝贵的:核心*2+硬盘数,在 4 核 cpu 下,这个值仅仅为 9。c#可能有一套更优的公式,但也离这个值不远。一旦 worker thread 中运行了同步阻塞式代码,宝贵的线程池资源会被浪费。可能造成 threadpool exhaustion 等问题(这个我不是很懂),但至少会让吞吐量下降到 线程池大小 /响应时间。

    相比来说,感觉 go 的并发代码编写方式、内部实现都很可以。希望 .Net Core 早日达到 go 的性能。
    crclz
        24
    crclz  
       2019-11-08 01:01:13 +08:00
    @xingheng @xuanbg 回复一直发不出来,截断后发出来了。
    loqixh
        25
    loqixh  
       2019-11-08 09:53:31 +08:00
    @crclz 吐血, asp.net core 一直超过 go 的网络性能啊........虽然比不上 java 的 netty 但是 Netty 用得人不多, 所以日常来说.net core 性能基本是最高之一
    crclz
        26
    crclz  
       2019-11-08 10:06:41 +08:00
    @loqixh 网上我看过一些评测,发现都是 go (的某个框架)优于.net core
    loqixh
        27
    loqixh  
       2019-11-08 10:18:01 +08:00
    @crclz 因为都是用玩具框架对比.net core 的全功能框架,就算这样 go 的性能也就比.net core 高 10%-20%的样子, 真正实用性能可以看 aws lambda 的性能测试.net core 是其中最高的
    xingheng
        28
    xingheng  
       2019-11-08 14:29:16 +08:00
    @xuanbg 你说得对,是我上面对“并发量”的描述不对,应该算 keepalive connection/request 比较准确。
    xingheng
        29
    xingheng  
       2019-11-08 15:05:31 +08:00
    @crclz 学习了。我单独去查了一下“effective_spindle_count”的几篇文章,你说的 connection pool 大小是指的数据库层面的,不是 http/tcp connection 的。我觉得跟题主说的 API 层面上的 async 不太对等。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3015 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 14:39 · PVG 22:39 · LAX 06:39 · JFK 09:39
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.