高并发不是“加线程就完事”,而是要分清任务类型、选对机制、避开默认陷阱。C# 里真正扛住万级 QPS 的系统,几乎从不靠
Thread.Start()或盲目
Task.Run(),而是组合使用异步 I/O、并发集合、轻量同步和架构分流。
async/await 不是语法糖,是 I/O 并发的唯一正解
如果你还在用
dbContext.SaveChanges()同步写数据库,或用
HttpClient.GetStringAsync().Result等响应,那你的服务在 200 并发时就可能线程池饥饿、响应延迟飙升甚至死锁。 所有外部调用(HTTP、DB、文件、Redis)必须走真正的异步 API:用
ToListAsync()而非
ToLIst(),用
GetStreamAsync()而非
GetResponse()绝不能在 async 方法里调用
.Wait()或
.Result—— ASP.NET Core 中这会直接卡死请求上下文 高频小响应(如鉴权、计数器)优先用
ValueTask<t></t>替代
Task<t></t>,避免 GC 压力(比如每秒 10 万次 token 校验,
ValueTask可减少 30%+ 内存分配)
public async ValueTask<bool> IsUserActiveAsync(int userId)
{
// ✅ 正确:异步查缓存 + 异步查 DB(必要时)
var cached = await _cache.GetAsync<bool>($"user:active:{userId}");
if (cached.HasValue) return cached.Value;
<pre class='brush:php;toolbar:false;'>return await _db.Users
.Where(u => u.Id == userId && u.Status == "Active")
.AnyAsync();}
别乱锁,先看是不是真需要“写”——读多写少场景用 ReaderWriterLockSlim
很多人一遇到并发就上
lock(obj),结果整个方法变成串行瓶颈。其实 80% 的共享状态是“读远多于写”,比如配置项、用户权限白名单、限流计数器。
lock是排他锁,读操作也得排队;而
ReaderWriterLockSlim允许多个读同时进行,只在写时阻塞全部读写 注意:它不自动释放,必须配对
EnterReadLock()/ExitReadLock()或用
using模式(.NET 6+ 支持
using var _ = rwLock.EnterReadLock();) 如果只是计数累加,优先用
Interlocked.Increment(ref count)—— 零锁、原子、比 lock 快 5–10 倍
CPU 密集任务别扔进线程池,用 Parallel + 显式控制并发度
把图像压缩、JSON 解析、规则引擎计算这类 CPU 绑定任务丢进
Task.Run(),等于主动制造线程风暴。16 核机器跑 100 个
Task.Run(() => HeavyCalc()),结果是上下文切换压垮吞吐。 用
Parallel.ForEach(source, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }) 控制并行数
大集合处理优先用 PLINQ:data.AsParallel().Where(...).Select(...).ToArray(),但注意它默认不保留顺序,且异常会包装成
AggregateException更重的任务(如批量报表生成)应剥离到后台服务 + 消息队列,Web 层只返回 “任务已提交,ID: xxx”,避免占用请求线程
单机扛不住?那就别硬扛——用分布式缓存 + 消息队列做“软性分流”
再优化的代码也抵不过一个没缓存的热点查询(比如首页 Banner),或一个同步发邮件的接口。高并发的本质不是“让单机更快”,而是“让不该在请求链路里做的事,根本不出现在链路里”。
用户会话、商品详情、配置中心数据 → 全部进Redis,设置合理过期时间,用
IDistributedCache接口解耦 订单创建后发短信、更新搜索索引、扣减库存 → 封装成消息,投递到
RabbitMQ或
Kafka,由独立消费者处理 千万别在 HTTP 请求里直接
await _queue.PublishAsync(msg)后等 ACK —— 改为 fire-and-forget(或至少设超时 100ms),失败由死信队列兜底
最常被忽略的一点:高并发问题往往不是出在“怎么写”,而是出在“怎么测”。本地用
HttpClient循环发 1000 次请求 ≠ 真实并发 —— 缺少连接复用、TCP 拥塞、服务端队列积压都测不出来。上线前务必用
wrk或
hey做真实连接级压测,并监控
ThreadPool.GetAvailableThreads()和 GC 代龄分布。
