C#之高并发处理过程

 更新时间:2026年05月25日 16:46:35   作者:陈同学呀  
本文深入探讨了C#中处理高并发问题的策略,包括异步编程、分布式架构与数据库优化,异步编程通过`async/await`避免线程阻塞,提升系统性能;分布式架构如消息队列和缓存则实现解耦与削峰填谷;数据库优化则强调连接池与异步操作,确保高效处理大量并发请求

高并发本质是系统在单位时间内处理大量并行请求的能力。

在C#中处理这个问题需要分层解决:首先是架构层面,比如是否采用分布式;然后是语言特性层面,比如异步编程;最后是基础设施层面,比如数据库优化。

1.异步编程(async/await)

核心思想

避免阻塞线程。当一个操作(如 I/O - 文件读写、网络请求、数据库查询)需要等待外部资源时,释放当前线程去处理其他请求,待操作完成后再由线程池分配线程继续执行。

优势

显著提高线程池线程的利用率(一个线程可处理多个请求),用更少的线程服务更多的并发请求,减少线程上下文切换开销,提高系统吞吐量和响应能力。

C# 实现

 使用 async 关键字标记异步方法,在需要等待的操作前使用 await 关键字。

public async Task<ActionResult> GetData()
{
    var data = await _dbContext.Data.ToListAsync(); // 异步数据库操作
    return Ok(data);
}

关键点

  • 所有 I/O 操作(数据库、网络、文件)必须异步

  • 避免 Task.Wait() 或 Task.Result(会导致死锁)

2. 分布式架构

(1) 消息队列(削峰填谷)

核心思想: 

  • 将耗时的、非实时的操作(如发送邮件、生成报告、复杂数据处理)异步化。
  • 请求到达后,将任务信息放入消息队列(如 RabbitMQ, Azure Service Bus, Kafka, Amazon SQS),立即返回响应。
  • 后台有专门的“消费者”进程从队列中取出消息并处理。

优势:

  • 削峰填谷: 突发的高流量可以被队列缓冲,消费者按自身能力消费,避免后端服务瞬时过载崩溃。
  • 解耦: 生产者和消费者完全解耦,互不影响,提高系统可靠性和可维护性。
  • 异步处理: 释放 Web 服务器线程,使其专注于处理用户请求。
  • 重试机制: 消息队列通常支持消息传递失败后的重试。
// 使用 RabbitMQ.Client 发送
using var channel = _connection.CreateModel();
channel.QueueDeclare("orders");
var body = Encoding.UTF8.GetBytes(orderJson);
channel.BasicPublish("", "orders", body); // 异步解耦
  • 推荐工具RabbitMQ、Kafka、Azure Service Bus

(2)分布式缓存

核心思想: 

  • 将频繁读取但不经常变化的数据(如配置、热门商品信息、会话状态)存储在独立于应用服务器、高性能的内存缓存服务(如 Redis, Memcached中。

优势:

  • 极大减少对后端数据库(通常是性能瓶颈)的访问次数。
  • 数据存储在内存中,访问速度极快。
  • 支持分布式部署,多个应用实例共享同一缓存,保证数据一致性。
  • 提高应用的可扩展性(水平扩展应用服务器时,缓存层通常更容易扩展)

推荐工具

  • Redis, Memcached
// 使用 StackExchange.Redis
IDatabase cache = Connection.GetDatabase();
await cache.StringSetAsync("key", "value", TimeSpan.FromMinutes(10));
string value = await cache.StringGetAsync("key");

3. 数据库优化

连接池: 

  • ADO.NET 和 ORM(如 EF Core)默认管理数据库连接池。

  • 确保配置合理的 MinPoolSize 和 MaxPoolSize

异步数据库操作 

  • 务必使用 ORM 或 ADO.NET 提供的异步方法(如 ToListAsync()SaveChangesAsync()ExecuteReaderAsync())来执行数据库查询和命令。

优化查询:

  • 使用索引避免全表扫描。
  • 只查询需要的字段(Select)。
  • 避免 N+1 查询问题(EF Core 中注意使用 Include 或投影)。
  • 合理设计数据模型。
  • 考虑读写分离、分库分表(在数据量极大时)。

NoSQL 考虑: 

  • 对于某些特定场景(如文档存储、键值对、宽列存储、图数据库),NoSQL 数据库(如 MongoDB, Cassandra, Cosmos DB)可能比关系型数据库(如 SQL Server, PostgreSQL)更适合高并发读写和水平扩展。

4. 使用锁(lock)和互斥量(Mutex)

  • 核心思想: 当多个线程需要访问共享资源(如静态变量、单例实例、文件句柄)时,必须协调它们的访问,防止数据损坏或状态不一致。

  • 常用机制:lock 语句: 最常用,基于 Monitor 类,提供互斥锁。

虽然锁在高并发场景下可能会引起性能瓶颈,但在某些情况下仍然需要使用。确保只在必要时使用,并尽可能减少锁定范围。

private object lockObject = new object();
public void ProcessData(int data)
{
    lock (lockObject)
    {
        // 执行需要同步的代码块
    }
}

乐观锁和Redis分布式锁都是处理高并发场景的核心方案

乐观锁

乐观锁 通过版本号(Version)或时间戳(Timestamp)实现“无锁”并发控制:

  • 读阶段:读取数据时记录当前版本号。
  • 写阶段:提交更新前校验版本号是否未变化,若变化则重试或失败。

典型实现包括:

  • 数据库乐观锁:通过SQL语句(如UPDATE ... WHERE version=old_version)。
  • Redis的WATCH/MULTI:监视Key变化,事务中执行CAS操作

Redis分布式锁

原理:

基于Redis的原子操作(如SET key value NX PX)实现互斥访问:

  • 加锁:通过SETNX设置唯一值并附加超时时间。
  • 续期:Redisson的Watchdog线程自动延长锁有效期。
  • 释放:校验持有者身份后删除Key。

适用场景

  • 低冲突场景:如读多写少的业务(商品浏览、配置读取)。

  • 突发流量:秒杀系统中库存扣减(配合重试机制)。

  • 需高吞吐:避免锁竞争,提升并发能力

乐观锁和Redis分布式锁是处理高并发的互补方案而非互斥:

  • 乐观锁:轻量、高吞吐,适合低冲突场景,需防范ABA问题。
  • Redis分布式锁:强一致、易用,需优化主从容错与锁粒度。

实际系统中常组合使用(如Redis锁拦截请求+数据库乐观锁保证最终一致)

Monitor 类: lock 的底层实现,提供更细粒度控制(如 TryEnterWaitPulse)。

Mutex: 进程间或跨 AppDomain 的互斥锁,重量级。

 //当前电脑只能启动一个WCS程序
 using (System.Threading.Mutex m = new System.Threading.Mutex(true, AppConfig.Instance.GetConfig("主界面名称"), out Started))
 {
     if (Started)
     {
         if (DbManagerBase.Instance.GetDbTime() == false)
         {
             MessageBox.Show("数据库连接失败,请检查原因!");
         }
         else
         {
             Application.EnableVisualStyles();
             Application.SetCompatibleTextRenderingDefault(false);
             Application.Run(new FrmMain());
         }
     }
     else
     {
         LibManager.WriteLog("WCS程序启动失败,与运行中WCS程序的主界面名称重复", RecordLogTypeEnum.Error.ToString(), "");
         MessageBox.Show("当前WCS程序已启动!");
     }
 }

关键原则:

  • 最小化锁范围: 只在绝对必要的地方加锁,并尽快释放锁。

  • 避免锁嵌套: 容易导致死锁。

  • 锁粒度: 根据共享资源的范围选择合适的锁(细粒度锁通常并发性更好)。

  • 优先使用并发集合: 在可能的情况下,用 ConcurrentDictionary 等代替自己用锁实现的集合

5. 使用线程池(ThreadPool)和Task.Run

对于需要大量并发线程的场景,可以使用Task.Run来启动一个新任务到线程池中。

.NET 运行时维护一个预先创建的线程池,用于执行后台任务和处理异步回调。

这比手动创建和销毁线程更高效。

Task[] tasks = new Task[10];
for (int i = 0; i < 10; i++)
{
    tasks[i] = Task.Run(() => DoWork());
}
await Task.WhenAll(tasks);

典型误区

  1. 过度使用 Task.Run:I/O 操作直接用 async 而非封装线程

  2. 全局静态锁:导致无谓的线程阻塞

  3. 同步调用异步方法GetAwaiter().GetResult() 引发死锁

  4. 忽略连接池配置:数据库连接成为瓶颈

黄金法则

异步化所有 I/O 路径 + 避免共享状态 + 队列解耦

通过负载测试(如 JMeter/LoadRunner)持续验证系统极限。

6. 并发集合 (System.Collections.Concurrent)

核心思想

  •  提供线程安全的集合类,允许多个线程安全地添加、移除或访问集合中的元素,而无需开发者手动加锁。

常用类

  • ConcurrentQueue<T>: 先进先出 (FIFO) 队列。
  • ConcurrentStack<T>: 后进先出 (LIFO) 栈。
  • ConcurrentDictionary<TKey, TValue>: 键值对字典。
  • ConcurrentBag<T>: 无序集合,适用于对象池等场景。
  • BlockingCollection<T>: 提供阻塞和限制功能的集合,常用于生产者-消费者模式。

优势: 

简化多线程编程,避免锁竞争(内部使用高效的锁或无锁技术),提高并发访问性能。

示例: 

使用 ConcurrentQueue 实现简单的生产者-消费者。

using System.Collections.Concurrent;


private static readonly ConcurrentDictionary<Type, string> assemblyQualifiedNameCache = new ConcurrentDictionary<Type, string>();

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • C# 如何实现一个基于值相等性比较的字典

    C# 如何实现一个基于值相等性比较的字典

    这篇文章主要介绍了C# 如何实现一个基于值相等性比较的字典,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下
    2021-02-02
  • C#中值类型和引用类型的区别深度分析

    C#中值类型和引用类型的区别深度分析

    这篇文章主要介绍了C#中值类型和引用类型的区别深度分析,以通俗易懂的语言形象化的分析了C#中值类型和引用类型的区别,对于深入理解C#数据类型有着不错的参考借鉴价值,需要的朋友可以参考下
    2014-11-11
  • 使用C#快速搭建一个在windows运行的exe应用

    使用C#快速搭建一个在windows运行的exe应用

    这篇文章主要介绍了使用C#快速搭建一个在windows运行的exe应用,这是一个比较旧的内容,但是一直都没有空写,今天花点时间,把我掌握的C# 分享给初学的人或者感兴趣的人,希望能对你有一定帮助,感兴趣的小伙伴跟着小编一起来看看吧
    2024-07-07
  • C#读取目录下所有指定类型文件的方法

    C#读取目录下所有指定类型文件的方法

    这篇文章主要介绍了C#读取目录下所有指定类型文件的方法,可实现读取目录下特定后缀名文件的功能,需要的朋友可以参考下
    2015-06-06
  • 基于NPOI用C#开发的Excel以及表格设置

    基于NPOI用C#开发的Excel以及表格设置

    这篇文章主要为大家详细介绍了基于NPOI用C#开发的Excel以及表格设置,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • C#防SQL注入代码的三种方法

    C#防SQL注入代码的三种方法

    这篇文章主要介绍了C#防SQL注入代码的三种方法,有需要的朋友可以参考一下
    2014-01-01
  • C#判断字符串中是否包含指定字符串及contains与indexof方法效率问题

    C#判断字符串中是否包含指定字符串及contains与indexof方法效率问题

    这篇文章主要介绍了C#判断字符串中是否包含指定字符串及contains与indexof方法效率问题 ,文中给大家列举通过两种方法来判断,需要的朋友可以参考下
    2018-10-10
  • C# Winform调用百度接口实现人脸识别教程(附源码)

    C# Winform调用百度接口实现人脸识别教程(附源码)

    这篇文章主要介绍了C# Winform调用百度接口实现人脸识别教程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05
  • 深入理解C#之接口

    深入理解C#之接口

    这篇文章主要介绍了C#接口(Interface)用法,较为详细的分析了C#中接口的功能、实现及使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2021-07-07
  • 基于WPF实现步骤控件的示例代码

    基于WPF实现步骤控件的示例代码

    这篇文章主要为大家详细介绍了WPF实现简单的步骤控件,文中的示例代码讲解详细,对我们学习或工作有一定帮助,感兴趣的小伙伴可以了解一下
    2023-01-01

最新评论