You need to enable JavaScript to run this app. C#未按序触发的配对异步方法,如何实现A_i→对应B_i有序执行
优惠活动
大模型
产品
解决方案
定价
更多

C#未按序触发的配对异步方法,如何实现A_i→对应B_i有序执行

更简洁的实现思路

你现在的实现本质上是靠两个互斥量强行给A、B的调用分别排队,再用TCS做跨类型的唤醒,其实完全可以跳出“分别排队、互相唤醒”的思路,换成基于状态的流水线调度——毕竟你的场景有两个非常明确的前置条件:

  • A、B本身的触发就是严格按序号顺序来的,不存在插队、乱序触发的情况
  • 最终要的执行序列是固定的 A₁→B₁→A₂→B₂→A₃→B₃... 循环

这种场景根本不需要复杂的FIFO信号量,只要维护一个简单的状态机,明确“当前步骤该执行A还是B”,所有进来的调用只要匹配当前步骤就直接执行,不匹配就挂起等唤醒就行,整个代码量能减少一半,还能避开原来实现里的几个常见坑。

具体实现

你可以把这套调度逻辑封装成一个独立的协调器类,核心只需要一个锁、一个步骤计数、一个等待TCS,完全不需要额外的信号量:

public class SequentialABRunner
{
    // 记录下一个要执行的步骤:偶数步对应A,奇数步对应B,从0开始(第一步是A1)
    private int _nextStep = 0;
    // 挂起等待的任务入口,因为触发本身是严格有序的,只需要存一个就行
    private TaskCompletionSource _waitHandle;
    // 保护共享状态的轻量锁
    private readonly object _stateLock = new();

    // A方法的统一入口
    public async Task RunAAsync(Func<Task> aWork)
    {
        Task runTask;
        lock (_stateLock)
        {
            // 当前该执行偶数步的A,直接执行
            if (_nextStep % 2 == 0)
            {
                _nextStep++;
                runTask = ExecuteWorkWithStateUpdate(aWork);
            }
            // 还没轮到A,挂起等唤醒
            else
            {
                _waitHandle = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
                runTask = _waitHandle.Task.ContinueWith(async _ => 
                    await ExecuteWorkWithStateUpdate(aWork), TaskScheduler.Default);
            }
        }
        await runTask;
    }

    // B方法的统一入口
    public async Task RunBAsync(Func<Task> bWork)
    {
        Task runTask;
        lock (_stateLock)
        {
            // 当前该执行奇数步的B,直接执行
            if (_nextStep % 2 == 1)
            {
                _nextStep++;
                runTask = ExecuteWorkWithStateUpdate(bWork);
            }
            // 还没轮到B,挂起等唤醒
            else
            {
                _waitHandle = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
                runTask = _waitHandle.Task.ContinueWith(async _ => 
                    await ExecuteWorkWithStateUpdate(bWork), TaskScheduler.Default);
            }
        }
        await runTask;
    }

    // 执行完工作后更新状态,唤醒下一个等待的任务
    private async Task ExecuteWorkWithStateUpdate(Func<Task> work)
    {
        await work();
        lock (_stateLock)
        {
            _waitHandle?.TrySetResult();
            _waitHandle = null;
        }
    }
}

方案优势

  • 没有冗余逻辑:不需要分别为A、B建两个FIFO信号量,因为你本来就保证了触发顺序是严格有序的,根本不需要额外的排队机制。整个调度逻辑只靠状态计数推进,就算原来的FIFO信号量实现有唤醒顺序的bug,这套逻辑也不会出错。
  • 状态更可控:所有共享状态都在锁的保护下更新,不存在原来实现里TCS为null或者错配的问题。你甚至可以很轻松地在状态计数里加上序号校验,就算出现重复调用、漏调用的异常场景,也能快速定位问题。
  • 避坑更彻底:原实现里用async void定义异步方法是非常危险的——只要方法里抛出未捕获异常,会直接导致进程崩溃。这套实现的入口都是async Task,可以正常向上传递异常、支持外部await,不会出现方法执行完就“消失”的问题。

适配原有代码

原来的两个方法只需要把核心逻辑抽到Work里,用协调器包一层就行:

var runner = new SequentialABRunner();

// 替换原来的AAsync
async Task AAsync()
{
    await runner.RunAAsync(async () =>
    {
        // 原来AAsync里的核心逻辑
        await Task.Delay(100); // 模拟异步操作
    });
}

// 替换原来的BAsync
async Task BAsync()
{
    await runner.RunBAsync(async () =>
    {
        // 原来BAsync里的核心逻辑
        await Task.Delay(100); // 模拟异步操作
    });
}

用你举的乱序触发序列 A₁A₂B₁B₂A₃B₃B₄A₄ 测试,最终实际的执行顺序会严格按照要求输出 A₁→B₁→A₂→B₂→A₃→B₃→A₄→B₄,完全匹配需求。


内容的提问来源于stack exchange,提问作者Miguel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.27 10:24:04