异步/等待高性能服务器应用程序
本文关键字:服务器 应用程序 高性能 等待 异步 | 更新日期: 2023-09-27 17:58:08
C# 5 中新的 async/await 关键字看起来很有前途,但我读了一篇关于对这些应用程序的性能影响的文章,因为编译器将为异步方法生成一个相当复杂的状态机。
使用这些关键字进行异步编程要容易得多,但它是否与套接字的SocketAsyncEventArgs一样好?
第二个问题:像Stream.WriteAsync这样的异步IO方法真的是异步的(.Net上的完成端口或Mono上的epoll/poll(,还是这些方法便宜的包装器,用于将写入调用推送到线程池?
第三个问题:除了 UI 应用程序的同步上下文之外,有没有办法实现某种 sinlge 线程上下文?类似于事件循环的东西,以便完成的任务在主线程上继续?我发现了Nito.AsyncEx库,但我不太确定这是否是我需要的。
async
本身就非常高性能。为此做了大量工作。
通常,在服务器端,您担心async
I/O。我将忽略async
CPU 密集型方法,因为无论如何,async
开销都会在噪音中丢失。
异步 I/O 会增加每个请求的内存使用量,但会减少每个请求的线程使用率。所以你最终赢了(边缘病理性极端情况除外(。这适用于所有异步 I/O,包括 async
。
await
设计有模式 - 不仅仅是Task
类型 - 所以如果你需要挤出尽可能多的性能,你可以。
我读了一篇关于对这些应用程序的性能影响的文章,因为编译器将为异步方法生成一个相当复杂的状态机。
你读的斯蒂芬·图布的文章非常好。我还推荐Zen of Async视频(也是Stephen Toub的(。
使用这些关键字进行异步编程要容易得多,但它是否与套接字的SocketAsyncEventArgs一样好?
首先,了解SocketAsyncEventArgs
更具可扩展性,因为它减少了内存垃圾。使用async
套接字的更简单方法会产生更多的内存垃圾,但由于await
是基于模式的,您可以为 SocketAsyncEventArgs
API 定义自己的async
兼容包装器(如 Stephen Toub 的博客所示......我在这里感觉到了一种模式;)。这使您可以榨取每一盎司的性能。
尽管从长远来看,设计横向扩展系统通常更好,而不是扭曲代码以避免一些内存分配。恕我直言。
第二个问题:像Stream.WriteAsync这样的异步IO方法真的是异步的(.Net上的完成端口或Mono上的epoll/poll(,还是这些方法便宜的包装器,用于将写入调用推送到线程池?
我不知道单声道。在 .NET 上,大多数异步 I/O 方法都基于完成端口。Stream
类是一个值得注意的例外。默认情况下,Stream
基类将执行"廉价包装器",但允许派生类重写此行为。 来自网络通信Stream
始终覆盖此内容,以提供真正的异步 I/O。 仅当流是为异步 I/O 显式构造时,处理文件的Stream
才会覆盖此内容。
第三个问题:除了UI应用程序的同步上下文之外,有没有办法实现某种单线程上下文?
ASP.NET 还有一个 SynchronizationContext
,所以如果你正在使用 ASP.NET 你已经设置好了。
如果您正在做自己的基于套接字的服务器(例如,Win32 服务(,那么您可以使用我的 AsyncEx 库中的 AsyncContext
类型。但听起来这不是你真正想要的。 AsyncContext
将在当前线程上创建单线程上下文。但是,服务器应用程序的async
的真正功能来自扩展请求而不是线程。
考虑 ASP.NET SynchronizationContext
是如何工作的:当每个请求进来时,它会获取一个线程池线程并构造一个SynchronizationContext
(针对该请求(。当该请求有异步工作要执行时,它会向SynchronizationContext
注册,运行该请求的线程将返回到线程池。稍后,当异步工作完成时,它会获取线程池线程(任何线程(,在其上安装现有SynchronizationContext
,并继续处理该请求。当请求最终完成时,将释放其SynchronizationContext
。
该过程的关键是,当请求等待(await
(异步操作时,没有专用于该请求的线程。由于与线程相比,请求相当轻量级,因此这使服务器能够更好地扩展。
如果您为每个请求提供单线程SynchronizationContext
例如 AsyncContext
,这将绑定一个线程到每个请求,即使它无关。这几乎不比同步多线程服务器好。
您可能会发现我的 MSDN 文章SynchronizationContext
如果您想解决发明自己的SynchronizationContext
问题很有用。我还在那篇文章中介绍了异步方法如何"注册"和"安装"上下文;这主要由 async void
和 await
自动完成,因此您不必明确执行此操作。
-
如果您在异步 IO 的上下文中使用它,这是一个有争议的问题。 花在数据库操作、文件/网络 IO 等上的时间最多只有几毫秒。
async
的开销在最坏的情况下将是微秒,如果没有纳秒的话。 您需要注意的是,当您有很多操作正在等待(如数千、数万或更多(并且这些操作非常快时。 当这些异步操作表示 CPU 密集型工作时,使用await
的开销肯定至少是显而易见的。 请注意,就人类的理解能力而言,为状态机生成的代码有些复杂,但状态机通常总体上表现得相当好。 -
这些方法不仅仅是阻塞线程池线程的包装器,不是。 这将违背
await
所代表的目的。 这些方法不会阻塞任何线程,而是依靠操作系统挂钩来完成任务。 -
当然,您可以创建自己的
SynchronizationContext
,而不是完全依赖UI框架提供的现有。 这是一个很好的例子。 使用这样的东西时要小心;它是完成正确任务的好工具,但是当您应该异步执行所有操作时,可能会滥用以找到一种更具创建性的阻止方式。