C#基于HttpListener的服务高并发请求处理性能优化求助
问题根因分析
- 服务端核心处理逻辑同步阻塞:
ProcessResponse方法中使用Thread.Sleep(2000)同步阻塞工作线程,单个请求会占用线程2秒,即使线程池满负载运行,处理能力也只能达到线程数/2 次/秒,远达不到数千级的性能要求。 - 线程参数配置错误:
HTTP_HANDLER_THREADS设置为5000完全无效,.NET线程池和操作系统都不会同时创建这么多线程,过多的线程只会带来额外的上下文切换开销,反而降低性能。该参数合理值为CPU核心数的2~4倍。 - 请求处理流程存在多处同步IO:
StreamReader.ReadToEnd()为同步读取方法,会阻塞线程等待IO完成,浪费线程资源。 - 响应写入逻辑存在缺陷:
OutputStream.WriteAsync()未等待异步操作完成就直接调用Close(),会导致部分响应数据丢失,请求失败率升高。 - 压测代码逻辑存在问题:
HttpClient.PostAsync是异步方法,代码未添加await等待请求完成就统计发送数,计数完全失真;Parallel.For默认并发度有限,且HttpClient默认的最大连接数限制会导致大量请求排队,无法真实模拟高并发场景。
优化方案
服务端优化
- 全异步改造处理流程:将同步阻塞逻辑全部替换为异步实现,
Thread.Sleep改为await Task.Delay,同时将请求读取、处理、响应写入全链路改为异步,避免阻塞工作线程。 - 调整线程池参数:将
HTTP_HANDLER_THREADS调整为CPU核心数的24倍(例如8核CPU设置为1632),无需设置过大。 - 修正响应写入逻辑:等待异步写入完成后再关闭输出流,避免数据丢失。
优化后的核心代码示例:
// 首先将ProcessDataDelegate改为异步委托 public delegate Task<byte[]> ProcessDataDelegate(string request); // 改造ProcessRequestHandler为异步 private async void ProcessRequestHandler(Task<HttpListenerContext> result) { var context = result.Result; if (!listener.IsListening) return; // 提前启动下一个上下文监听 listener.GetContextAsync().ContinueWith(ProcessRequestHandler); try { // 异步读取请求 using var reader = new StreamReader(context.Request.InputStream); var request = await reader.ReadToEndAsync(); // 异步执行业务处理 var response_bytes = await handler.Invoke(request); context.Response.ContentLength64 = response_bytes.Length; // 异步写入响应并等待完成 using var output = context.Response.OutputStream; await output.WriteAsync(response_bytes, 0, response_bytes.Length); } catch (Exception ex) { // 可添加异常处理逻辑 context.Response.StatusCode = 500; } finally { context.Response.Close(); } } // 改造ProcessResponse为异步方法 private static async Task<byte[]> ProcessResponse(string response) { // 线程安全累加计数 Interlocked.Increment(ref request_amount); Console.WriteLine(request_amount); // 异步等待模拟业务耗时,不阻塞线程 await Task.Delay(2000); return Array.Empty<byte>(); }
压测端优化
- 修正异步调用逻辑:添加
await等待PostAsync执行完成,保证统计准确。 - 解除HttpClient连接限制:启动时设置
ServicePointManager.DefaultConnectionLimit = 1024,解除默认的2连接/域名的限制。 - 采用合理的并发控制:可使用信号量
SemaphoreSlim限制并发数,避免瞬间请求量过大压垮测试端。
优化后的压测代码示例:
class Program { private static readonly HttpClient client = new HttpClient(); private const int send_amount = 200000; private static int sent_request = 0; // 控制并发数为1000,可根据实际情况调整 private static readonly SemaphoreSlim semaphore = new SemaphoreSlim(1000); static async Task Main(string[] args) { // 解除连接限制 ServicePointManager.DefaultConnectionLimit = 1024; var tasks = new List<Task>(); for (int i = 0; i < send_amount; i++) { await semaphore.WaitAsync(); tasks.Add(SendRequestAsync(i)); } await Task.WhenAll(tasks); Console.WriteLine("所有请求发送完成"); } private static async Task SendRequestAsync(int index) { try { var values = new Dictionary<string, string> { { "request", index.ToString() } }; var content = new FormUrlEncodedContent(values); var response = await client.PostAsync("http://192.168.102.165:1990", content); response.EnsureSuccessStatusCode(); Interlocked.Increment(ref sent_request); Console.WriteLine($"Sent request {sent_request}/{send_amount}"); } finally { semaphore.Release(); } } }
按照以上方案优化后,服务端即可达到每秒数千次的请求处理能力。如果业务处理逻辑本身无耗时,可将Task.Delay移除,性能会进一步提升。
内容的提问来源于stack exchange,提问作者Deathcriw
相关产品推荐
相关产品推荐

