C#教程

关注公众号 jb51net

关闭
首页 > 软件编程 > C#教程 > C#高并发处理

C#之高并发处理过程

作者:陈同学呀

本文深入探讨了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);
}

关键点

2. 分布式架构

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

核心思想: 

优势:

// 使用 RabbitMQ.Client 发送
using var channel = _connection.CreateModel();
channel.QueueDeclare("orders");
var body = Encoding.UTF8.GetBytes(orderJson);
channel.BasicPublish("", "orders", body); // 异步解耦

(2)分布式缓存

核心思想: 

优势:

推荐工具

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

3. 数据库优化

连接池: 

异步数据库操作 

优化查询:

NoSQL 考虑: 

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

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

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

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

乐观锁

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

典型实现包括:

Redis分布式锁

原理:

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

适用场景

乐观锁和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程序已启动!");
     }
 }

关键原则:

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 实现简单的生产者-消费者。

using System.Collections.Concurrent;


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

总结

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

您可能感兴趣的文章:
阅读全文