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

如何在C#中实现所有事件触发后才执行回调方法?

问题

我希望延迟某个方法的执行,直到它所订阅的所有事件都各自触发一次。例如,某个方法可能是两个独立事件的回调:

classA.OnEvent += SomeMethod;
classB.OnEvent += SomeMethod;

我希望SomeMethod()仅在这两个事件都触发后才执行,或者在通用场景下,仅在n个事件都触发后执行。想知道C#中是否有现成的机制可以实现这一需求,目前我用的是自定义代码:

using System;

public class Program
{
    public static void Main()
    {
        var publisherA = new Publisher();
        var publisherB = new Publisher();
        var subscriber = new Subscriber(publisherA, publisherB);
        publisherA.DoSomething();
        publisherB.DoSomething();
    }
}

public class Publisher 
{
    public event Action OnSomethingHappened;

    public void DoSomething() 
    {
        OnSomethingHappened?.Invoke();
    }
}

public class Subscriber
{
    private bool a;
    private bool b;

    public Subscriber(Publisher publisherA, Publisher publisherB) 
    {
        publisherA.OnSomethingHappened += RecordPublisherA;
        publisherB.OnSomethingHappened += RecordPublisherB;
    }

    private void RecordPublisherA() 
    {
        Console.WriteLine("Recording publisherA");
        a = true;
        BothPublisherEventsTriggered();
    }

    private void RecordPublisherB() 
    {
        Console.WriteLine("Recording publisherB");
        b = true;
        BothPublisherEventsTriggered();
    }

    private void BothPublisherEventsTriggered() 
    {
        if (!a || !b)
            return;
    
        Console.WriteLine("Both publisher events triggered");
        a = b = false;
    }
}
解决方案

C#中有几种现成的机制可以实现这种“等待多个事件各触发一次再执行目标方法”的需求,比自定义布尔标记更通用、更易维护:

1. 使用CountdownEvent同步原语

CountdownEvent是.NET提供的专门用于等待多个操作完成的同步类,初始化时指定需要等待的事件数量,每次事件触发时调用Signal()减少计数,当计数归0时,会触发等待完成的逻辑。

可以封装一个通用的订阅器:

using System;
using System.Threading;

public class EventWaiter
{
    private readonly CountdownEvent _countdown;
    private readonly Action _targetMethod;
    private readonly object _lockObj = new object();

    public EventWaiter(int eventCount, Action targetMethod)
    {
        _countdown = new CountdownEvent(eventCount);
        _targetMethod = targetMethod;
    }

    // 绑定事件的通用回调
    public void OnEventTriggered()
    {
        lock (_lockObj)
        {
            if (_countdown.CurrentCount == 0) return; // 防止重复触发
            
            _countdown.Signal();
            if (_countdown.IsSet)
            {
                _targetMethod.Invoke();
                _countdown.Reset(); // 重置计数,支持重复等待
            }
        }
    }
}

使用方式:

public class Program
{
    public static void Main()
    {
        var publisherA = new Publisher();
        var publisherB = new Publisher();
        var waiter = new EventWaiter(2, () => Console.WriteLine("Both publisher events triggered"));
        
        publisherA.OnSomethingHappened += waiter.OnEventTriggered;
        publisherB.OnSomethingHappened += waiter.OnEventTriggered;
        
        publisherA.DoSomething();
        publisherB.DoSomething();
    }
}

2. 使用TaskCompletionSource结合Task.WhenAll(异步场景)

如果你的场景适合异步编程,可以把每个事件转换成Task,然后用Task.WhenAll等待所有任务完成后执行目标方法,这种方式不需要阻塞线程:

using System;
using System.Linq;
using System.Threading.Tasks;

public class AsyncEventWaiter
{
    private TaskCompletionSource<bool>[] _tcsArray;
    private readonly Action _targetMethod;
    private readonly object _lockObj = new object();

    public AsyncEventWaiter(int eventCount, Action targetMethod)
    {
        _tcsArray = new TaskCompletionSource<bool>[eventCount];
        ResetTcsArray();
        _targetMethod = targetMethod;
        _ = WaitForAllEvents();
    }

    private async Task WaitForAllEvents()
    {
        while (true)
        {
            await Task.WhenAll(_tcsArray.Select(tcs => tcs.Task));
            _targetMethod.Invoke();
            // 重置所有TaskCompletionSource,支持重复等待
            lock (_lockObj)
            {
                ResetTcsArray();
            }
        }
    }

    private void ResetTcsArray()
    {
        for (int i = 0; i < _tcsArray.Length; i++)
        {
            _tcsArray[i] = new TaskCompletionSource<bool>();
        }
    }

    // 每个事件绑定一个独立的回调,标记对应的任务完成
    public Action GetEventCallback(int index)
    {
        return () => _tcsArray[index].TrySetResult(true);
    }
}

使用方式:

public class Program
{
    public static void Main()
    {
        var publisherA = new Publisher();
        var publisherB = new Publisher();
        var waiter = new AsyncEventWaiter(2, () => Console.WriteLine("Both publisher events triggered"));
        
        publisherA.OnSomethingHappened += waiter.GetEventCallback(0);
        publisherB.OnSomethingHappened += waiter.GetEventCallback(1);
        
        publisherA.DoSomething();
        publisherB.DoSomething();
        
        Console.ReadLine();
    }
}

对比自定义实现的优势

  • 通用性:上述两种方式都支持任意数量的事件,不需要为每个事件单独定义布尔变量和回调方法
  • 可维护性:逻辑集中在通用类中,修改需求(比如调整事件数量、修改触发逻辑)时不需要改动多个地方
  • 线程安全:内置的同步原语和锁机制比手动维护布尔变量更可靠,避免多线程场景下的竞态问题

内容的提问来源于stack exchange,提问作者Ginger and Lavender

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 21:27:46