前几天,我遇到了一个开发人员挑战异步服务器端代码的使用.他问,在没有UI线程可阻止的服务器上,为什么异步代码优于同步代码?我给了他一个典型的线程耗尽的答案,但经过一段时间的思考,我不再确定我的答案是正确的.在做了一些研究之后,我发现操作系统中线程的上限是由内存决定的,而不是由任意数字决定的.像Kestrel这样的服务器支持无限线程.所以在"理论"中,服务器可以并行阻止的请求(线程)数量由内存控制.这与中的异步代码没有什么不同.网它将堆栈变量提升到堆中,但仍然是内存受限的.

我一直认为,比我聪明的人都有这样的 idea ,异步代码是处理IO绑定代码的正确方法.但是异步有哪些可测量的优势呢.NET代码在没有UI线程的专用服务器场中运行时?移动到云端(AWS)会改变答案吗?

推荐答案

Server-side asynchronous code purpose is completely different from asynchronous UI code.
Asynchronous UI code makes UI more responsive (especially when multiple CPU cores are available), it allows multiple UI tasks to run in parallel which improves UI user experience.
The purpose of server-side asynchronous code on the other hand is to minimise the resources necessary to serve multiple clients simultaneously. In fact it is beneficial even if there is only one CPU core or a single-threaded event loop like in Node.js. And it all boils down to a simple concept of Asynchronous IO.

同步IO和异步IO之间的区别在于,对于前者,初始化IO操作的线程将暂停,直到IO操作完成(例如,直到执行DB请求或读取磁盘上的文件).一旦IO操作完成,同一个线程就会被取消暂停,以处理其结果.注意:尽管暂停时线程很可能没有使用任何CPU资源(它可能被线程调度程序置于睡眠状态),但它的资源仍然与这个特定的IO操作有关,并且在硬件执行IO时浪费了大量资源.有效地使用同步IO,每个当前正在处理的客户端请求至少需要一个线程,即使这些线程中的大多数可能正在Hibernate ,等待IO操作完成.在里面NET每个线程至少分配了1MB的堆栈,因此,如果服务器当前正在处理1000个请求,那么只为线程堆栈分配了近1GB的内存,再加上线程调度程序的额外负担,以及CPU花费在上下文切换上的时间:线程越多,系统的整体性能越低.分配的内存越多,内存/CPU缓存的使用效率也越低.

异步IO效率更高,因为工作线程只初始化一个IO操作,而不是等待它完成,它会立即切换到另一个有用的任务(例如,继续另一个客户端的请求处理),当硬件完成IO操作时,结果的处理会在任何可用的工作线程上恢复.因此,根据等待硬件完成IO的总时间与执行CPU任务(例如,将IO操作结果序列化为JSON)的时间之间的比率,这种方法可以使用less threads来服务相同数量的同时客户端请求:如果,90%的时间都花在IO上,我们可能只使用less threads个线程来处理同样的less threads0个并发请求.服务器端代码越是受IO约束而不是CPU约束,它就越能使用给定的资源量(CPU和内存)处理并发的客户端请求.

异步代码的缺点是什么?主要是,它通常比同步更难写.异步代码使用回调来恢复操作,因此程序员需要将委托(延续)传递给IO方法,而不是简单的线性代码.IO操作完成后(可能在不同的线程上),系统会调用该方法.然而,拥有async/await个功能的现代C#使这项任务变得不那么复杂,甚至使异步代码看起来几乎像是同步的.唯一需要记住的是:异步代码只有在"一路向下"异步时才能工作:从初始HTTP请求处理到DB请求调用的调用堆栈中的某个地方,即使是单个Task.WaitTask.Result,也会使整个代码同步,从而迫使当前工作线程等待Wait次调用完成,以达到预期目的.注:await in C#代码实际上并不等待调用的结果,而是由编译器转换为ContinueWith,即转换为延续回调,虽然实际上是more complicated than that位,但幸运的是程序员隐藏了复杂性,因此现在编写高效的异步代码是一项相对简单的任务.

Csharp相关问答推荐

使用C#中的SDK在Microsoft Graph API上使用SubscribedSkus的问题

不带身份的Blazor服务器.Net 8 Cookie身份验证

有没有办法使.NET 6应用程序在特定的.NET 6运行时版本上运行

HttpConext.Request.Path和HttpConext.GetEndpoint()之间的差异

JsonSerializer.Deserialize<;TValue>;(String,JsonSerializerOptions)何时返回空?

如何在毛伊岛应用程序中完美地同步视图模型和视图的加载?

C#-VS2022:全局使用和保存时的代码清理

CS1660无法将lambda表达式转换为类型INavigationBase,因为它不是委托类型

显示文档的ECDsa签名PDF在Adobe Reader中签名后已被更改或损坏

如何在C#中创建VS代码中的控制台应用程序时自动生成Main方法

如何在用户在线时限制令牌生成?

EFR32BG22 BLE在SPP模式下与PC(Windows 10)不连接

.NET 8 DI GetServices<;对象&>不工作

将操作从编辑页重定向到带参数的索引页

为什么Docker中没有转发该端口?

Azure Functions v4中的Serilog控制台主题

具有嵌套属性的IGGroup

如何在Xamarin.Forms中检索PanGesture事件的位置?

我如何为我的Blazor应用程序构建一个动态教程标注?

C#:我需要根据换行符拆分字符串,而不是在字符串中用双引号分隔换行符