c# C#的异步Socket和同步Socket的性能差异

来源:这里教程网 时间:2026-02-21 17:39:21 作者:

同步Socket在高并发下会迅速卡死

同步调用

Socket.Receive()
Socket.Send()
时,线程会一直阻塞直到数据收发完成。哪怕只是处理几百个长连接,用
Thread
每连接起一个线程,很快就会耗尽线程池资源,出现大量
ThreadAbortException
或响应延迟飙升。这不是代码写得不好,而是模型本身无法横向扩展。

异步Socket真正靠的是
SocketAsyncEventArgs
复用

很多人以为用

BeginReceive
/
EndReceive
就算“异步”了,其实那是基于线程池的伪异步,开销不小。真正高性能的做法是预分配一批
SocketAsyncEventArgs
实例,反复
SetBuffer
+
AcceptAsync
/
ReceiveAsync
,避免每次收发都 new 对象、触发 GC。关键点:

SocketAsyncEventArgs
必须手动调用
Dispose()
(通常在连接关闭时)
缓冲区最好用
ArrayPool<byte>.Shared.Rent()</byte>
管理,而不是每次都
new byte[8192]
ReceiveAsync
返回
false
表示同步完成,
true
才进回调 —— 别默认以为一定异步

吞吐量差距在真实场景中可达 5–10 倍

用相同硬件压测一个回显服务(单机 4 核 8G):

同步模式(每连接一线程):约 1200 QPS,CPU 95% 时连接数卡在 300 左右  
异步模式(单线程 EventLoop + SocketAsyncEventArgs 池):稳定 8500+ QPS,CPU 利用率 65%~75%

差距主要来自三方面:

内存分配:同步模式每连接至少多出 1MB 线程栈 + 频繁 buffer new;异步模式 buffer 复用后 GC 压力极低 上下文切换:300 个线程调度开销远高于 1 个线程处理 3000 个 socket 的 I/O 完成通知 系统调用密度:异步模式下
WSARecv
调用更紧凑,更容易被 IOCP 批量投递

别忽略
IOCompletionPort
的隐式绑定成本

.NET 的

Socket
异步方法底层走 Windows IOCP,但首次调用
ReceiveAsync
时才会把 socket 关联到线程池的完成端口 —— 这个过程有微小延迟。如果连接建立后立刻发数据,可能因端口未就绪导致第一次收包稍慢。解决办法很简单:

AcceptAsync
成功后,立即对新 socket 调用一次空 buffer 的
ReceiveAsync
(不等回调返回),强制绑定
或改用
ThreadPool.UnsafeQueueUserWorkItem
启动一个轻量初始化任务,提前触发绑定

这个细节在压测初期不容易暴露,但上线后偶发的首包延迟,往往就卡在这里。

相关推荐