c#如何使用SemaphoreSlim限制并发?底层原理是什么?

来源:菜鸟站长作者:广州程序员头衔:程序员
导读:本期聚焦于小伙伴创作的《c#如何使用SemaphoreSlim限制并发?底层原理是什么?》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《c#如何使用SemaphoreSlim限制并发?底层原理是什么?》有用,将其分享出去将是对创作者最好的鼓励。

在c#的并发编程场景中,当多个线程或异步任务需要访问有限的共享资源时,直接无限制地并发执行很容易导致资源耗尽、数据不一致等问题,SemaphoreSlim就是专门用来控制并发访问数量的同步原语,它比传统的Semaphore更轻量,更适合异步场景使用。

c#如何使用SemaphoreSlim限制并发?底层原理是什么?

SemaphoreSlim基础使用方式

同步场景下的并发限制

如果是同步执行的任务,我们可以通过Wait方法获取信号量,任务完成后通过Release方法释放信号量,下面的示例限制最多同时有3个任务执行。

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

class Program
{
    // 初始化SemaphoreSlim,初始可用信号量为3,最大信号量也为3
    static SemaphoreSlim semaphore = new SemaphoreSlim(3, 3);

    static void Main()
    {
        // 启动10个同步任务
        for (int i = 0; i < 10; i++)
        {
            int taskId = i;
            Task.Run(() => ProcessTask(taskId));
        }

        Console.ReadLine();
    }

    static void ProcessTask(int taskId)
    {
        Console.WriteLine($"任务{taskId}等待获取信号量");
        // 获取信号量,没有可用信号量时会阻塞当前线程
        semaphore.Wait();
        try
        {
            Console.WriteLine($"任务{taskId}开始执行,当前时间:{DateTime.Now:HH:mm:ss}");
            // 模拟任务执行耗时
            Thread.Sleep(2000);
            Console.WriteLine($"任务{taskId}执行完成");
        }
        finally
        {
            // 释放信号量,让其他等待的任务可以获取
            semaphore.Release();
        }
    }
}

异步场景下的并发限制

在异步编程中,使用同步的Wait方法可能会导致线程池阻塞,这时候应该使用WaitAsync方法,它不会阻塞线程,而是返回可等待的任务。

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

class Program
{
    static SemaphoreSlim semaphore = new SemaphoreSlim(2, 2);

    static async Task Main()
    {
        // 启动8个异步任务
        var tasks = new Task[8];
        for (int i = 0; i < 8; i++)
        {
            int taskId = i;
            tasks[i] = ProcessAsyncTask(taskId);
        }

        await Task.WhenAll(tasks);
    }

    static async Task ProcessAsyncTask(int taskId)
    {
        Console.WriteLine($"异步任务{taskId}等待获取信号量");
        // 异步获取信号量,不会阻塞线程
        await semaphore.WaitAsync();
        try
        {
            Console.WriteLine($"异步任务{taskId}开始执行,当前时间:{DateTime.Now:HH:mm:ss}");
            // 模拟异步操作耗时
            await Task.Delay(1500);
            Console.WriteLine($"异步任务{taskId}执行完成");
        }
        finally
        {
            semaphore.Release();
        }
    }
}

SemaphoreSlim核心属性与方法

我们可以通过下表了解SemaphoreSlim的核心成员:

成员名称类型说明
CurrentCount属性获取当前可用的信号量数量,也就是还可以同时允许多少个任务进入临界区
Wait()方法同步等待获取信号量,没有可用信号量时阻塞当前线程
WaitAsync()方法异步等待获取信号量,不会阻塞线程,支持传入取消令牌
Release()方法释放一个信号量,增加可用信号量数量,返回释放前的可用数量
Dispose()方法释放SemaphoreSlim占用的资源,使用后建议调用

SemaphoreSlim底层原理深入解析

SemaphoreSlim的设计目标是轻量且适配异步场景,它的底层实现和传统的Semaphore有本质区别,没有依赖操作系统的内核对象,而是完全基于托管代码实现。

核心数据结构

SemaphoreSlim内部主要维护以下几个核心部分:

  • 一个int类型的计数器,记录当前可用的信号量数量,初始值由构造函数传入的初始信号量参数决定。
  • 一个ConcurrentQueue<TaskCompletionSource<bool>>队列,用来存储等待获取信号量的异步任务对应的等待源,当有信号量释放时,会从队列中取出等待源并设置结果,唤醒对应的异步任务。
  • 同步等待的场景下,会结合ManualResetEventSlim或者自旋等待的方式处理,避免直接阻塞线程池线程。

Wait与Release的执行逻辑

当调用Wait方法时,首先会原子性地减少可用信号量计数器,如果减少后计数器的值大于等于0,说明成功获取到信号量,方法直接返回。如果计数器小于0,说明没有可用信号量,当前线程会进入等待状态,直到其他任务调用Release方法释放信号量。

当调用WaitAsync方法时,同样先尝试原子性减少计数器,成功则直接返回完成的任务。如果失败,会创建一个TaskCompletionSource<bool>对象,放入内部的等待队列中,然后返回这个等待源对应的任务,外部通过await等待这个任务,不会阻塞线程。

调用Release方法时,会原子性地增加可用信号量计数器,然后检查内部的等待队列是否有等待的任务,如果有,就从队列中取出一个等待源,设置其结果为true,对应的异步任务就会被唤醒继续执行。如果是同步等待的场景,会唤醒一个等待的线程。

与Semaphore的区别

传统的Semaphore是内核级别的同步原语,依赖操作系统的内核对象,跨进程可用,但是创建和使用的开销更大。而SemaphoreSlim是用户态的实现,开销更小,速度更快,但是只能在同一进程内使用,不支持跨进程同步。如果不需要跨进程的场景,优先选择SemaphoreSlim。

使用注意事项

  • 获取信号量和释放信号量必须成对出现,最好放在try-finally块中,避免因为异常导致信号量没有释放,造成死锁。
  • 初始化时的最大信号量参数不能小于初始信号量参数,否则会抛出异常。
  • 异步场景下不要混用WaitWaitAsync,避免不必要的线程阻塞。
  • 使用完成后建议调用Dispose方法释放资源,尤其是长期运行的应用中。
需要注意的是,SemaphoreSlim限制的是获取信号量的任务数量,而不是线程数量,在异步场景中,一个线程可以执行多个异步任务,所以SemaphoreSlim更适合控制异步任务的并发数量。

通过上面的内容,我们不仅掌握了SemaphoreSlim的基础用法,还了解了它的底层实现逻辑,在实际开发中可以根据场景合理选择使用方式,更好地处理并发访问的问题。

SemaphoreSlimcsharp并发控制异步编程修改时间:2026-07-03 07:18:31

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。