C# TAP模式下如何使用Semaphore实现任务循环轮转执行
问题根因
原有实现仅执行一轮就退出、也无法保证固定轮转顺序的核心原因有两点:
- 任务执行逻辑没有外层循环,每个任务抢到一次信号量、完成单次打印后就直接走完方法返回,不会重复执行。
- 单信号量仅能实现同一时间只有一个任务执行的互斥效果,完全无法控制执行顺序:
SemaphoreSlim官方没有承诺等待队列是严格FIFO的,就算给逻辑加循环,受线程调度影响也会出现同一个任务连续多次抢到信号量的情况,做不到按任务编号固定轮转。
改造方案
采用「单任务独立信号量+顺序传递信号」的实现方式,从机制上保证执行顺序完全符合轮转要求:
- 给10个任务各分配一个初始计数为0的专属
SemaphoreSlim,任务没收到自己的信号就一直阻塞等待,不会出现抢锁乱序的问题。 - 维护全局打印计数器,累计打印满100次时触发终止逻辑。
- 每个任务的执行逻辑套入循环:等待自身信号→判断是否达到终止条件→执行打印→释放下一个任务的专属信号量,形成闭环轮转。
- 启动时仅释放第一个任务的信号量,让整个轮转从Task#1开始。
可直接运行的代码实现
using System; using System.Threading; using System.Threading.Tasks; class RotatePrintDemo { private const int TotalPrintTimes = 100; private const int TaskTotalCount = 10; // 每个任务对应独立的信号量,初始计数为0,未收到信号时持续等待 private static readonly SemaphoreSlim[] _taskWaitHandles = new SemaphoreSlim[TaskTotalCount]; // 全局计数器,记录已完成的打印次数 private static int _printedCount = 0; static async Task Main(string[] args) { // 初始化所有任务的信号量 for (int i = 0; i < TaskTotalCount; i++) { _taskWaitHandles[i] = new SemaphoreSlim(0, 1); } // 启动10个打印任务 Task[] printTasks = new Task[TaskTotalCount]; for (int i = 0; i < TaskTotalCount; i++) { int currentTaskId = i + 1; printTasks[i] = RunSinglePrintTask(currentTaskId, i); } // 释放第一个任务的信号,启动轮转 _taskWaitHandles[0].Release(); await Task.WhenAll(printTasks); Console.WriteLine("100次轮转打印全部完成"); } static async Task RunSinglePrintTask(int taskId, int ownSemaphoreIndex) { while (true) { // 等待属于自己的执行信号 await _taskWaitHandles[ownSemaphoreIndex].WaitAsync(); // 已经打满100次时,传递信号给下一个任务后退出 if (_printedCount >= TotalPrintTimes) { int nextIndex = (ownSemaphoreIndex + 1) % TaskTotalCount; _taskWaitHandles[nextIndex].Release(); break; } // 执行打印 _printedCount++; Console.WriteLine($"第{_printedCount}次打印:Task#{taskId}"); // 计算下一个要执行的任务,释放对应信号量 int nextSemaphoreIndex = (ownSemaphoreIndex + 1) % TaskTotalCount; _taskWaitHandles[nextSemaphoreIndex].Release(); } } }
关键注意事项
- 终止逻辑必须向下传递信号量:如果达到100次打印后直接退出、不释放下一个任务的信号,剩余任务会一直卡在等待信号的状态,程序无法正常退出。
- 该实现严格按照Task#1到Task#10的顺序轮转,第1、11、21…91次打印固定由Task#1执行,完全不会出现乱序问题。
- 不要尝试靠调整单信号量的逻辑实现顺序控制,只要存在多任务抢锁的逻辑,就无法从根本上避免调度导致的顺序错乱。
内容的提问来源于stack exchange,提问作者sahmad
相关产品推荐
相关产品推荐

