async 本身的性能非常好。为此做了很多工作。
一般来说,在服务器端,您关心的是async I/O。我将忽略async CPU-bound 方法,因为async 开销无论如何都会在噪音中消失。
异步 I/O 会增加每个请求的内存使用量,但会减少每个请求的线程使用量。所以你最终会赢(边缘病态的极端案例除外)。这适用于所有异步 I/O,包括 async。
await 被设计成一个模式 - 不仅仅是 Task 类型 - 所以如果你需要尽可能多地挤出性能,你可以。
我阅读了一篇关于对这些应用程序的性能影响的文章,因为编译器将为异步方法生成一个相当复杂的状态机。
Stephen Toub 的 article you read 非常棒。我还推荐 Zen of Async video(也是 Stephen Toub 的)。
使用这些关键字进行异步编程要容易得多,但它和 SocketAsyncEventArgs for Sockets 一样好吗?
首先,了解SocketAsyncEventArgs 更具可扩展性,因为它减少了内存垃圾。使用async 套接字的更简单方法会产生更多内存垃圾,但由于await 是基于模式的,您可以使用define your own async-compatible wrappers for the SocketAsyncEventArgs API(如Stephen Toub 的博客中所见......我在这里感觉到了一种模式;)。这使您可以充分发挥性能。
尽管从长远来看,设计横向扩展系统通常比扭曲代码以避免一些内存分配更好。恕我直言。
第二个问题:像 Stream.WriteAsync 这样的异步 IO 方法真的是异步的吗(.Net 上的 Completion Ports 或 Mono 上的 epoll/poll)或者这些方法是用于将写入调用推送到线程池的廉价包装器?
我不知道 Mono。在 .NET 上,大多数 异步 I/O 方法基于完成端口。 Stream 类是一个明显的例外。 Stream 基类默认会做一个“廉价包装”,但允许派生类覆盖这个行为。来自网络通信的Streams 总是覆盖它以提供真正的异步 I/O。处理文件的Streams 仅覆盖此如果流是为异步 I/O 显式构造的。
第三个问题:除了 UI 应用的 SynchronizationContext 之外,有没有办法实现某种单线程上下文?
ASP.NET 也有一个SynchronizationContext,所以如果您使用的是 ASP.NET,那么您已经设置好了。
如果您正在做自己的基于套接字的服务器(例如,Win32 服务),那么您可以在我的 AsyncEx 库中使用 AsyncContext 类型。但听起来这并不是你真正想要的。 AsyncContext 将在当前线程上创建一个单线程上下文。但是,async 对于服务器应用程序的真正威力来自扩展请求,而不是线程。
考虑 ASP.NET SynchronizationContext 的工作原理:当每个 request 进来时,它会抓取一个线程池线程并构造一个 SynchronizationContext(针对那个 request )。当该请求有异步工作要做时,它向SynchronizationContext 注册,并且运行该请求的线程返回到线程池。稍后,当异步工作完成时,它会抓取一个线程池线程(any 线程),在其上安装现有的SynchronizationContext,并继续处理该请求。当请求最终完成时,其SynchronizationContext 被释放。
该过程的关键在于,当请求等待 (await) 异步操作时,没有 个线程专用于该请求。由于 request 与 thread 相比是相当轻量级的,这使得服务器能够更好地扩展。
如果你给你的每个请求一个单线程的SynchronizationContext,比如AsyncContext,这会为每个请求绑定一个线程,即使它没有任何关系。这几乎不比同步多线程服务器好。
如果您想创建自己的SynchronizationContext,您可能会发现我的MSDN article on SynchronizationContext 很有用。在那篇文章中,我还介绍了异步方法如何“注册”和“安装”上下文;这大部分是由async void 和await 自动完成的,因此您不必明确地执行此操作。