news 2026/4/23 19:15:33

异步编程,相关锁的介绍,SemaphoreSlim 信号量

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
异步编程,相关锁的介绍,SemaphoreSlim 信号量
  1. 关于SemaphoreSlim 信号量的使用注意事项
    SemaphoreSlim 类 (System.Threading)
  • Wait/Release 成对性(try/finally);
  • 嵌套 Wait 的死锁问题;
  • 必须为 Wait 设置超时;
  • 异步场景 WaitAsync 的正确使用;
  • 重复Release/未Wait就Release的异常;
  • 跨线程 Release 的逻辑混乱;
  • 安全封装的最佳实践
  1. Wait/Release 成对性(try/finally)
    风险点:若线程获取信号量后(Wait),因异常、逻辑跳转等未执行 Release,信号量计数无法恢复,后续线程会永久阻塞在 Wait 上(死锁)。
    规避方法:用 try/finally 包裹 Wait 后的逻辑,确保无论是否异常,Release 都会执行。
    错误示例(无 finally,异常导致不 Release):
privatereadonlySemaphoreSlim_globalSemaphore=new(2,2);privateasyncTaskTest1_WaitReleasePair(){// 错误示例:无finally,异常导致Release未执行Console.WriteLine("→ 错误示例(无finally):");try{_globalSemaphore.Wait();Console.WriteLine("错误示例:获取信号量后抛出异常");thrownewInvalidOperationException("模拟操作失败");// Release永远执行不到,信号量计数永久减少_globalSemaphore.Release();}catch(Exceptionex){Console.WriteLine($"错误示例异常:{ex.Message}");Console.WriteLine($"信号量当前计数:{_globalSemaphore.CurrentCount}(应为1,实际少1)");}}privatereadonlySemaphoreSlim_globalSemaphore=new(2,2);

正确示例(try/finally 保证 Release):

privateasyncTaskTest1_WaitReleasePair_Safe(){// 正确示例:try/finally兜底,避免异常导致不ReleaseConsole.WriteLine("\n→ 正确示例(try/finally):");try{_globalSemaphore.Wait();Console.WriteLine("正确示例:获取信号量后抛出异常");thrownewInvalidOperationException("模拟操作失败");}catch(Exceptionex){Console.WriteLine($"正确示例异常:{ex.Message}");}finally{_globalSemaphore.Release();Console.WriteLine($"finally执行Release,信号量当前计数:{_globalSemaphore.CurrentCount}(恢复为2)");}}
  1. SemaphoreSlim嵌套 Wait 的死锁问题
    同一线程多次 Wait 同一信号量,会消耗多个计数;若计数不足,内部 Wait 会阻塞,而线程本身持有信号量未释放,导致其他线程也无法释放,最终死锁。
  2. 死锁本质:SemaphoreSlim 是「无所有权的计数信号量」,无 “线程持有计数” 的记录,仅做计数增减;同一线程嵌套 Wait 会持续消耗计数,当计数耗尽后,线程自身阻塞在 Wait 上,无法执行 Release 恢复计数,形成「自死锁」。
    一般在全局变量的SemaphoreSlim,多个方法嵌套使用的时候需要注意。
// ❌ 危险代码:导致死锁privateSemaphoreSlim_semaphore=newSemaphoreSlim(1);publicasyncTaskDangerousMethodAsync(){await_semaphore.WaitAsync();// 在持有锁的情况下,等待另一个也需要相同锁的操作awaitAnotherMethodThatAlsoUsesTheSemaphoreAsync();// 死锁!_semaphore.Release();}publicasyncTaskAnotherMethodThatAlsoUsesTheSemaphoreAsync(){await_semaphore.WaitAsync();// 这个等待永远不会返回,因为锁被DangerousMethodAsync占用了try{/* 一些操作 */}finally{_semaphore.Release();}}
  1. 必须为 Wait 设置超时,避免无限阻塞
    风险点:若 Wait() 无超时,线程会无限等待信号量;若信号量因 BUG(如 Release 遗漏)永远无法释放,线程会永久阻塞(死锁)。
    规避方法:使用 Wait(int millisecondsTimeout) 或 Wait(CancellationToken),判断是否成功获取信号量,失败则直接退出。
    错误示例(无超时,无限等待):
// 线程阻塞在Wait上,永远无法唤醒(死锁)publicvoidNoTimeoutWait(){_semaphore.Wait();// 无超时,若信号量计数为0,永久阻塞try{DoWork();}finally{_semaphore.Release();}}

正确示例(带超时,失败则处理):

publicvoidTimeoutWait(){// 等待1秒,获取失败则返回falseboolacquired=_semaphore.Wait(1000);if(!acquired){// 超时处理(如日志、重试),避免死锁Console.WriteLine("获取信号量超时,放弃执行");return;}try{DoWork();}finally{_semaphore.Release();}}
  1. 异步场景 WaitAsync 的正确使用
    风险点:在 UI 线程(如 WPF)或异步上下文用 Wait()(同步阻塞),会占用线程且无法释放信号量,导致死锁。规避方法:用 WaitAsync() 配合 async/await,异步等待不阻塞线程,确保后续 Release 能执行。
privateasyncvoidBtn_Click(objectsender,RoutedEventArgse){// 正确示例:异步场景用WaitAsync,无阻塞+上下文连续Console.WriteLine("\n→ 正确示例(异步场景WaitAsync):");try{awaitsemaphore.WaitAsync();// 异步等待,不阻塞线程Console.WriteLine("WaitAsync:获取信号量");stringdata=awaitTask.Run(()=>{Thread.Sleep(1000);return"模拟数据";});Console.WriteLine($"WaitAsync:线程切换后数据={data}");}finally{semaphore.Release();// 同一异步流,逻辑成对Console.WriteLine("WaitAsync:Release执行(安全)");}}
  1. 重复Release/未Wait就Release的异常
    风险点:
  • 未 Wait 直接 Release:会导致信号量计数超过最大值,后续 Wait 逻辑混乱,可能引发线程安全问题;
  • 重复 Release:同样导致计数异常,若计数溢出,会抛 SemaphoreFullException,进而导致后续线程无法正常获取信号量(间接死锁)。
    错误示例(重复 Release):
publicvoidDuplicateRelease(){_semaphore.Wait();try{DoWork();}finally{_semaphore.Release();// 计数+1(正确)_semaphore.Release();// 重复Release,计数超出最大值,抛异常}}

SemaphoreSlim:本质是计数信号量,仅管理 “可用计数”,不校验 Wait/Release 是否在同一线程(Release 只做计数 + 1,不管是谁调用。

  1. Wait 后线程切换的典型场景(异步 + WPF 高发),跨线程 Release 的逻辑混乱
    最常见的线程切换场景是:在 async 方法中用同步 Wait(SemaphoreSlim.Wait()),后续 await 导致线程切换,最终 Release 跑在另一个线程,引发计数异常。
    错误示例(WPF 中 Wait 后线程切换,Release 错位)
privatestaticreadonlySemaphoreSlim_semaphore=new(1,1);// 单线程并发// WPF 按钮点击事件(UI线程)privateasyncvoidBtn_Click(objectsender,RoutedEventArgse){// 步骤1:UI线程调用Wait,获取信号量(计数1→0)_semaphore.Wait();try{// 步骤2:await 触发线程切换(UI线程释放,后台线程执行)stringdata=awaitTask.Run(()=>{Thread.Sleep(1000);return"后台数据";});// 步骤3:await 后切回UI线程(逻辑上还是原上下文,但如果是控制台/ASP.NET,可能切到线程池其他线程)// 问题:若此处不是UI线程(比如ASP.NET),Release 就跑在非 Wait 的线程上ResultText.Text=data;}finally{// 风险:Release 线程 ≠ Wait 线程(虽SemaphoreSlim不抛异常,但计数逻辑易乱)// 极端情况:若await后线程被销毁/阻塞,Release 执行时机不可控,导致计数异常_semaphore.Release();}}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 12:32:34

5个实用技巧:让任务管理工具真正为你服务

5个实用技巧:让任务管理工具真正为你服务 【免费下载链接】siyuan A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. 项目地址: https://gitcode.com/GitHub_Trending/si/siyuan …

作者头像 李华
网站建设 2026/4/23 15:54:13

U-2-Net深度学习模型:5分钟掌握工业缺陷检测核心技术

U-2-Net深度学习模型:5分钟掌握工业缺陷检测核心技术 【免费下载链接】U-2-Net U-2-Net - 用于显著对象检测的深度学习模型,具有嵌套的U型结构。 项目地址: https://gitcode.com/gh_mirrors/u2/U-2-Net 还在为工业产品质量检测而烦恼吗&#xff1…

作者头像 李华
网站建设 2026/4/23 12:24:09

MusicFree歌单迁移终极解决方案:跨平台音乐收藏完整指南

你是否曾经因为更换音乐平台而丢失了多年精心整理的个人歌单?🤔 面对不同音乐平台的版权壁垒和封闭生态,实现歌单的无缝迁移似乎成为了一项不可能完成的任务。MusicFree作为一款插件化、定制化的免费音乐播放器,通过其强大的歌单导…

作者头像 李华
网站建设 2026/4/23 15:30:58

Gemma-3 270M轻量级AI模型:如何在普通电脑上运行多模态大模型

Gemma-3 270M轻量级AI模型:如何在普通电脑上运行多模态大模型 【免费下载链接】gemma-3-270m-it-bnb-4bit 项目地址: https://ai.gitcode.com/hf_mirrors/unsloth/gemma-3-270m-it-bnb-4bit 想要在普通电脑上体验多模态AI的强大功能吗?Google最新…

作者头像 李华
网站建设 2026/4/23 12:24:16

为什么说Kitty是Windows终端的最佳选择?

为什么说Kitty是Windows终端的最佳选择? 【免费下载链接】kitty Cross-platform, fast, feature-rich, GPU based terminal 项目地址: https://gitcode.com/GitHub_Trending/ki/kitty 在Windows系统上寻找理想的终端工具往往令人困扰。传统命令行界面启动缓慢…

作者头像 李华
网站建设 2026/4/23 18:54:26

DBeaver数据导入终极指南:告别外键约束错误

DBeaver数据导入终极指南:告别外键约束错误 【免费下载链接】dbeaver 项目地址: https://gitcode.com/gh_mirrors/dbe/dbeaver 你是否曾经在导入多个数据文件时,因为顺序混乱而遭遇外键约束错误?或者面对几十个关联表时,不…

作者头像 李华