browser-agent进阶应用:如何实现多步骤复杂工作流自动化
2026/7/18 9:53:34
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)");}}// ❌ 危险代码:导致死锁privateSemaphoreSlim_semaphore=newSemaphoreSlim(1);publicasyncTaskDangerousMethodAsync(){await_semaphore.WaitAsync();// 在持有锁的情况下,等待另一个也需要相同锁的操作awaitAnotherMethodThatAlsoUsesTheSemaphoreAsync();// 死锁!_semaphore.Release();}publicasyncTaskAnotherMethodThatAlsoUsesTheSemaphoreAsync(){await_semaphore.WaitAsync();// 这个等待永远不会返回,因为锁被DangerousMethodAsync占用了try{/* 一些操作 */}finally{_semaphore.Release();}}// 线程阻塞在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();}}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执行(安全)");}}publicvoidDuplicateRelease(){_semaphore.Wait();try{DoWork();}finally{_semaphore.Release();// 计数+1(正确)_semaphore.Release();// 重复Release,计数超出最大值,抛异常}}SemaphoreSlim:本质是计数信号量,仅管理 “可用计数”,不校验 Wait/Release 是否在同一线程(Release 只做计数 + 1,不管是谁调用。
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();}}