如何跟踪EventHandler列表事件触发状态,触发后续事件且保留取消订阅能力?
解决方案
核心思路是通过订阅包装类跟踪每个事件的触发状态,同时保存用于取消订阅的委托实例——既解决匿名函数无法取消订阅的问题,又实现“所有事件至少触发一次后触发目标事件”的需求。
具体实现代码
// 跟踪单个事件的订阅状态与委托实例 private class EventSubscriptionTracker { // 保存与订阅时完全一致的委托实例,用于后续取消订阅 public EventHandler SubscriptionHandler { get; } public bool HasTriggered { get; private set; } public EventSubscriptionTracker(Action onAllEventsTriggered) { SubscriptionHandler = (sender, e) => { if (!HasTriggered) { HasTriggered = true; // 检查所有事件是否都已触发 if (_trackers.All(t => t.HasTriggered)) { onAllEventsTriggered?.Invoke(); } } }; } // 手动重置触发状态(如需重复检测场景) public void ResetTriggerState() { HasTriggered = false; } } // 类内成员 private List<EventSubscriptionTracker> _trackers = new List<EventSubscriptionTracker>(); // 所有事件触发完成后要触发的目标事件 public event EventHandler AllEventsCompleted; // 批量订阅事件并启动跟踪 public void SubscribeAndTrackEvents(List<EventHandler> eventHandlers) { // 先清理之前的订阅与跟踪状态 UnsubscribeAllTrackedEvents(eventHandlers); foreach (var handler in eventHandlers) { var tracker = new EventSubscriptionTracker(OnAllEventsCompleted); // 给目标事件添加包装后的处理委托 handler += tracker.SubscriptionHandler; _trackers.Add(tracker); } } // 批量取消所有订阅 public void UnsubscribeAllTrackedEvents(List<EventHandler> eventHandlers) { if (_trackers.Count != eventHandlers.Count) { throw new InvalidOperationException("跟踪器数量与事件列表不匹配"); } for (int i = 0; i < eventHandlers.Count; i++) { var handler = eventHandlers[i]; var tracker = _trackers[i]; // 使用保存的委托实例取消订阅 handler -= tracker.SubscriptionHandler; } _trackers.Clear(); } // 目标事件的触发逻辑 private void OnAllEventsCompleted() { AllEventsCompleted?.Invoke(this, EventArgs.Empty); // 若需支持重复检测,可在此重置所有跟踪器状态 // foreach (var tracker in _trackers) tracker.ResetTriggerState(); }
关键说明
- 规避匿名函数局限:
EventSubscriptionTracker类中创建的SubscriptionHandler是可复用的委托实例,订阅和取消订阅使用同一个实例,彻底解决匿名函数无法取消订阅的问题。 - 精准状态跟踪:每个跟踪器维护独立的
HasTriggered标记,仅在首次触发时更新状态,避免重复计数干扰判断。 - 批量管理能力:通过
_trackers列表统一管理所有事件的跟踪状态,支持批量取消订阅或重置触发状态。 - 线程安全优化(可选):如果事件在多线程环境下触发,可将
HasTriggered替换为原子操作标记,确保状态更新的线程安全:private int _hasTriggeredFlag; // 0=未触发,1=已触发 public bool HasTriggered => _hasTriggeredFlag == 1; // 在处理委托中替换原逻辑: if (Interlocked.CompareExchange(ref _hasTriggeredFlag, 1, 0) == 0) { // 执行后续检查逻辑 }
内容的提问来源于stack exchange,提问作者Permafrost
相关产品推荐
相关产品推荐

