C#教程

关注公众号 jb51net

关闭
首页 > 软件编程 > C#教程 > C# SemaphoreSlim并发控制与限流策略

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

作者:拾荒的小海螺

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

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

场景说明
并发 HTTP 请求控制限制同时发起的请求数
数据库连接池限制连接数量,防止连接耗尽
图片处理任务限制 CPU 密集型任务数
异步队列消费避免后台任务过载
API 限流防止接口被滥用或打爆

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

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

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