c# 编写高并发代码时,如何平衡性能和可读性

7次阅读

用 ValueTask 替代 Task 可避免堆分配,但仅限同步完成路径且不跨 await 边界重用;错误使用会抛 InvalidOperationException;应优先用于高频内部方法,公共 API 慎用,需复用时转 Task,禁用 ConfigureAwait(false)。

c# 编写高并发代码时,如何平衡性能和可读性

ValueTask 替代 Task 时要注意同步完成路径

同步返回的异步方法(比如缓存命中、参数校验失败)用 ValueTask 能避免堆分配,但前提是不跨 await 边界重用或暴露给外部。常见错误是把 ValueTask 存进字段、传给非 await 上下文(如 ContinueWith),或在未 await 前多次 await —— 这会抛 InvalidOperationException: "ValueTask may only be awaited once"

实操建议:

  • 只在 hot path(如高频 API 入口、内存缓存读取)中对确定「大概率同步完成」的方法用 ValueTask
  • 避免在公共 API 返回 ValueTask,除非你完全控制调用方行为;内部方法可放宽
  • 若需复用或延迟 await,先转成 Task
    var task = valueTask.AsTask();
  • 别用 ValueTask.ConfigureAwait(false) —— 它不存在,必须先转 Task

异步流处理优先选 IAsyncEnumerable,但别在循环里开新 async 方法

IAsyncEnumerable 是 C# 8+ 处理高并发数据流(如分页查库、实时日志推送)的自然选择,但它本身不解决并发度控制。常见陷阱是写成这样:

await foreach (var item in GetItemsAsync()) // 每次 yield 都可能触发一次 DB 查询 {await ProcessItemAsync(item); // 串行执行,吞吐掉一半 }

正确做法是用 Task.WhenAll 控制并发批次,同时保持流式内存友好:

  • BufferBlock(来自 System.Threading.Tasks.Dataflow)做生产者 - 消费者解耦
  • 限制并行度:用 Parallel.ForEachAsync(.NET 6+)并设 MaxDegreeOfParallelism
  • 若仍用 await foreach,提前批量化:var batch = items.Take(100).ToList(),再 Task.WhenAll(batch.Select(ProcessItemAsync))

锁不是唯一瓶颈,ConcurrentDictionaryImmutableArray 往往更轻量

高并发下盲目加 lockMonitor 容易成为争用 热点 ,尤其在短临界区(如更新计数器、查缓存)。ConcurrentDictionaryGetOrAddAddOrUpdate 无锁 设计,比手动 lock + Dictionary 快 3–5 倍(实测 .NET 6+)。

但要注意:

  • ConcurrentDictionary 的枚举不是线程安全快照,遍历时可能漏项或重复;需要完整快照就用 ToArray()
  • 高频小对象拼接(如日志上下文构建)用 ImmutableArray.BuilderList + lock 更省 GC
  • 纯读多写少场景,考虑 Lazy + ConcurrentDictionary 组合,避免重复初始化

可读性妥协点要显式标注,比如用 [SkipLocalsInit] 或内联 Span

极致性能优化(如零分配序列化、Socket 缓冲区复用)必然牺牲可读性。这时别藏技巧,用编译器特性或注释明确意图:

  • [SkipLocalsInit] 省掉 上数组初始化开销,但必须确保所有分支都赋值,否则行为未定义
  • 字符串解析 优先用 ReadOnlySpan + Span,避免 Encoding.UTF8.GetBytes(str) 分配;但只在 hot path 用,普通逻辑保持 string
  • 在方法名或 XML 注释里写清权衡:“// ⚠️ 零分配,但要求 input.Length

最难的不是写出高性能代码,而是让半年后的自己或同事一眼看出哪行是“为吞吐让步”,哪行是“真不能动”。可读性和性能冲突时,边界一定要划清楚——模糊地带最容易出偶发超时或内存泄漏。

星耀云
版权声明:本站原创文章,由 星耀云 2025-12-30发表,共计1699字。
转载说明:转载本网站任何内容,请按照转载方式正确书写本站原文地址。本站提供的一切软件、教程和内容信息仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。
text=ZqhQzanResources