C#使用SemaphoreSlim实现并发控制与限流策略的实战指南

 更新时间:2025年11月12日 09:05:24   作者:拾荒的小海螺  
在现代应用中(如爬虫、并发请求、数据库连接池、异步任务处理),我们常常需要限制同时执行的任务数量,以避免过载或资源竞争,在 C# 中,最简洁高效的解决方案之一就是SemaphoreSlim,本文就给大家介绍了C#使用SemaphoreSlim实现并发控制与限流策略的实战指南

1、简述

在现代应用中(如爬虫、并发请求、数据库连接池、异步任务处理),我们常常需要限制同时执行的任务数量,以避免过载或资源竞争。

在 C# 中,最简洁高效的解决方案之一就是 —— SemaphoreSlim

2、基本原理

SemaphoreSlim 内部维护一个“许可计数(Permit Count)”。
每个线程执行前需要调用 Wait()(或 WaitAsync())来获取许可,执行结束后调用 Release() 归还许可。

当所有许可被占用时,新线程会等待,直到有资源释放。SemaphoreSemaphoreSlim 都用于控制并发访问,但它们的实现和性能不同。

对比项SemaphoreSemaphoreSlim
实现方式内核对象(较重)用户态轻量实现(高性能)
是否跨进程✅ 是❌ 否
是否支持 async/await❌ 否✅ 是
推荐场景跨进程同步应用内并发控制

结论: 在现代 C# 应用中(如 Web API、后台任务、异步 I/O),使用 SemaphoreSlim 是首选。

3、实践样例

3.1 基本用法

using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static SemaphoreSlim semaphore = new SemaphoreSlim(3); // 最多3个并发

    static async Task Main()
    {
        var tasks = new Task[10];
        for (int i = 0; i < 10; i++)
        {
            int id = i;
            tasks[i] = Task.Run(() => DoWorkAsync(id));
        }

        await Task.WhenAll(tasks);
        Console.WriteLine("全部任务完成!");
    }

    static async Task DoWorkAsync(int id)
    {
        await semaphore.WaitAsync(); // 获取许可
        try
        {
            Console.WriteLine($"任务 {id} 开始,当前时间:{DateTime.Now:T}");
            await Task.Delay(1000); // 模拟工作
            Console.WriteLine($"任务 {id} 结束。");
        }
        finally
        {
            semaphore.Release(); // 释放许可
        }
    }
}

输出示例:

任务 0 开始...
任务 1 开始...
任务 2 开始...
任务 3 等待中...
任务 0 结束。
任务 3 开始...
...

说明:同时最多 3 个任务在运行。

3.2 限制并发的 HTTP 请求(爬虫场景)

using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

class WebCrawler
{
    static readonly HttpClient httpClient = new HttpClient();
    static readonly SemaphoreSlim semaphore = new SemaphoreSlim(5); // 最多5个并发

    static async Task Main()
    {
        var urls = new[]
        {
            "https://example.com",
            "https://dotnet.microsoft.com",
            "https://github.com",
            "https://openai.com",
            "https://google.com",
            "https://bing.com"
        };

        var tasks = new Task[urls.Length];
        for (int i = 0; i < urls.Length; i++)
        {
            string url = urls[i];
            tasks[i] = ProcessUrlAsync(url);
        }

        await Task.WhenAll(tasks);
        Console.WriteLine("所有请求完成!");
    }

    static async Task ProcessUrlAsync(string url)
    {
        await semaphore.WaitAsync();
        try
        {
            Console.WriteLine($"开始请求:{url}");
            var response = await httpClient.GetAsync(url);
            Console.WriteLine($"{url} 返回状态码:{response.StatusCode}");
        }
        finally
        {
            semaphore.Release();
        }
    }
}

效果:同一时间最多有 5 个请求在执行,防止网络或服务器过载。

3.3 控制文件写入任务

多个线程同时写同一个文件时,可以用 SemaphoreSlim 控制访问速率。

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

class FileWriter
{
    static SemaphoreSlim semaphore = new SemaphoreSlim(1); // 一次仅允许1个线程写文件

    static async Task Main()
    {
        var tasks = new Task[5];
        for (int i = 0; i < 5; i++)
        {
            int id = i;
            tasks[i] = Task.Run(() => WriteLogAsync(id));
        }

        await Task.WhenAll(tasks);
        Console.WriteLine("写入完成。");
    }

    static async Task WriteLogAsync(int id)
    {
        await semaphore.WaitAsync();
        try
        {
            await File.AppendAllTextAsync("output.txt", $"线程 {id} 在 {DateTime.Now}\n");
            Console.WriteLine($"线程 {id} 写入完成");
        }
        finally
        {
            semaphore.Release();
        }
    }
}

结果:日志文件不会被多个线程同时写入而损坏。

3.4 带超时机制的并发控制

如果等待太久无法获取资源,可设置超时防止卡死。

if (await semaphore.WaitAsync(TimeSpan.FromSeconds(2)))
{
    try
    {
        Console.WriteLine("成功获取资源。");
    }
    finally
    {
        semaphore.Release();
    }
}
else
{
    Console.WriteLine("等待超时,放弃操作。");
}

3.5 在异步队列中控制消费者数量

适用于后台任务或消息队列处理。

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

class QueueProcessor
{
    static SemaphoreSlim semaphore = new SemaphoreSlim(3); // 同时处理3个任务
    static BlockingCollection<int> queue = new BlockingCollection<int>();

    static async Task Main()
    {
        // 模拟生产者
        Task.Run(() =>
        {
            for (int i = 0; i < 10; i++)
            {
                queue.Add(i);
                Console.WriteLine($"生产任务 {i}");
                Thread.Sleep(200);
            }
            queue.CompleteAdding();
        });

        // 消费者
        var consumers = new Task[3];
        for (int i = 0; i < consumers.Length; i++)
        {
            consumers[i] = Task.Run(() => ConsumeAsync(i));
        }

        await Task.WhenAll(consumers);
        Console.WriteLine("队列处理完成。");
    }

    static async Task ConsumeAsync(int workerId)
    {
        foreach (var item in queue.GetConsumingEnumerable())
        {
            await semaphore.WaitAsync();
            try
            {
                Console.WriteLine($"工作线程 {workerId} 处理任务 {item}");
                await Task.Delay(500);
            }
            finally
            {
                semaphore.Release();
            }
        }
    }
}

效果:任务生产速度可以高于消费速度,但消费者数量始终受控,防止系统过载。

4、总结

通过本文,你已经掌握了:

  • SemaphoreSlim 的核心原理与区别;
  • 如何控制并发访问;
  • 在异步任务、HTTP 请求、文件 I/O 等场景下的实战用法;
  • 如何结合超时与限流策略提高系统稳定性。
场景说明
并发 HTTP 请求控制限制同时发起的请求数
数据库连接池限制连接数量,防止连接耗尽
图片处理任务限制 CPU 密集型任务数
异步队列消费避免后台任务过载
API 限流防止接口被滥用或打爆

SemaphoreSlim 是现代 .NET 并发编程中非常实用的工具, 能让你轻松实现资源保护、限流与异步协作。

以上就是C#使用SemaphoreSlim实现并发控制与限流策略的实战指南的详细内容,更多关于C# SemaphoreSlim并发控制与限流策略的资料请关注脚本之家其它相关文章!

相关文章

  • C#使用selenium实现操作浏览器并且截图

    C#使用selenium实现操作浏览器并且截图

    这篇文章主要为大家详细介绍了C#如何使用selenium组件实现操作浏览器并且截图,文中的示例代码简洁易懂,有需要的小伙伴可以参考一下
    2024-01-01
  • 详解c#与js的rsa加密互通

    详解c#与js的rsa加密互通

    这篇文章主要介绍了详解c#与js的rsa加密互通,帮助大家更好的理解和学习使用c#,感兴趣的朋友可以了解下
    2021-03-03
  • C#设置开机启动项、取消开机启动项

    C#设置开机启动项、取消开机启动项

    这篇文章主要介绍了C#设置开机启动项、取消开机启动项,本文通过修改注册实现,并给出操作代码,需要的朋友可以参考下
    2015-06-06
  • 字符串和十六进制之间的转换方法实例

    字符串和十六进制之间的转换方法实例

    这篇文章介绍了字符串和十六进制之间的转换方法实例,有需要的朋友可以参考一下
    2013-11-11
  • C# 实例化接口对象的方法

    C# 实例化接口对象的方法

    下面小编就为大家带来一篇C# 实例化接口对象的方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-02-02
  • C#创建WCF服务控制台应用程序详解

    C#创建WCF服务控制台应用程序详解

    这篇文章主要为大家详细介绍了C#创建WCF服务控制台应用程序,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07
  • C#线程同步的几种方法总结

    C#线程同步的几种方法总结

    在本篇文章里小编给大家整理的是关于C#线程同步的几种方法总结,需要的朋友们可以学习下。
    2020-02-02
  • c# 实现RSA非对称加密算法

    c# 实现RSA非对称加密算法

    RSA解决了对称加密的一个不足,比如AES算法加密和解密时使用的是同一个秘钥,因此这个秘钥不能公开,因此对于需要公开秘钥的场合,我们需要在加密和解密过程中使用不同的秘钥,加密使用的公钥可以公开,解密使用的私钥要保密,这就是非对称加密的好处。 
    2021-06-06
  • C#、vb.net及SQL判断指定年份是否为闰年的方法

    C#、vb.net及SQL判断指定年份是否为闰年的方法

    这篇文章主要介绍了C#、vb.net及SQL判断指定年份是否为闰年的方法,分别以三种方法实现了判断闰年的功能,在进行项目开发中具有一定的参考借鉴价值,需要的朋友可以参考下
    2014-11-11
  • C#中的事务用法实例分析

    C#中的事务用法实例分析

    这篇文章主要介绍了C#中的事务用法,以一个简单实例形式分析了C#创建及使用事物的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-09-09

最新评论