收到取消请求后,如何等待所有线程池工作项完成再退出?
问题解答
最佳实现方案
你可以仅通过现有SemaphoreSlim实现需求,无需引入额外同步原语。核心思路是:在收到取消信号后,尝试获取全部初始数量的许可——由于每个工作线程完成时会释放1个许可,只有当所有运行中的工作线程都完成、许可全部回归后,这个操作才会成功,从而实现阻塞等待所有工作项结束的效果。
修改后的代码
using System; using System.Threading; namespace ConsoleApp10 { class Program { static void Main() { var cts = new CancellationTokenSource(); var t = new Thread(Loop); t.Start(cts.Token); Console.WriteLine("Press any key to cancel."); Console.ReadKey(); Console.WriteLine("Cancelling loop"); cts.Cancel(); t.Join(); Console.WriteLine("At this point all worker threads must have exited."); Console.ReadLine(); } static void Loop(object state) { var ct = (CancellationToken)state; var numMaxThreads = 4; var sema = new SemaphoreSlim(numMaxThreads); try { while (true) { sema.Wait(ct); ThreadPool.QueueUserWorkItem(_ => { try { Work(); } finally { sema.Release(); } }); } } catch (OperationCanceledException) { // 等待所有工作项完成:获取全部初始许可,直到所有已使用的许可被释放 sema.Wait(numMaxThreads); // 释放许可(可选,因sema后续会被回收,但保持语义完整性) sema.Release(numMaxThreads); } } static void Work() { var r = new Random(Guid.NewGuid().GetHashCode()); Console.WriteLine($" {Thread.CurrentThread.ManagedThreadId}: Starting"); var until = DateTime.Now.AddMilliseconds(1000 + r.Next(5000)); while (true) { var left = until.Subtract(DateTime.Now); if (left <= TimeSpan.Zero) break; Console.WriteLine($" {Thread.CurrentThread.ManagedThreadId}: " + $"Working, Left: {left}"); Thread.Sleep(250 + r.Next(100) * 10); } Console.WriteLine($" {Thread.CurrentThread.ManagedThreadId}: Ending"); } } }
原理说明
- 并发限制逻辑:
SemaphoreSlim初始有4个许可,每次循环调用sema.Wait(ct)获取1个许可,只有获取成功才会提交工作线程,保证同时运行的工作线程不超过4个。 - 取消与等待逻辑:当取消信号触发时,
sema.Wait(ct)抛出OperationCanceledException进入catch块。此时调用sema.Wait(numMaxThreads)会阻塞,直到所有工作线程完成并释放许可(许可总数回到4),这就确保了所有当前运行的工作项都已结束。
内容的提问来源于stack exchange,提问作者user22260863
相关产品推荐
相关产品推荐

