You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 11:30:48