如何在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
相关产品推荐
相关产品推荐

