如何切换视频解码器的NewFrame事件委托?
这是个很典型的委托引用和事件订阅的坑,我来帮你理清楚问题所在,以及为什么你的最终方案是可行的:
为什么最初的代码不生效?
当你执行decoder.NewFrame += currentNewFrameDelegate;时,事件订阅的是当时currentNewFrameDelegate指向的playingStarted方法的引用。后续你修改currentNewFrameDelegate = imageReceived;,只是改变了这个变量的指向,但事件列表里保存的还是最初的playingStarted引用,所以每次触发NewFrame时,依然会调用playingStarted,完全不受变量修改的影响。
关于你提到的间接调用方案
你尝试的:
decoder.NewFrame += img => currentNewFrameDelegate?.Invoke(img);
其实这个思路是对的——通过一个固定的lambda作为事件的订阅者,让它去调用当前的委托变量。如果出现“双重调用”,大概率是你没有取消之前订阅的currentNewFrameDelegate(也就是playingStarted),导致事件列表里同时存在原来的委托和这个lambda,所以每次触发会执行两次。如果只订阅这个lambda,然后通过修改currentNewFrameDelegate的指向来切换处理逻辑,这个方案是可以正常工作的,只是相比你的最终方案,每次事件触发多了一层lambda的调用开销,对于实时视频处理来说,可能会有微小的性能影响。
你的最终方案:高效且易维护的正确姿势
你写的这个hookNewFrameEvent函数,完美解决了问题,而且非常适合你的场景:
Action<byte[]> current; void hookNewFrameEvent(Action<byte[]> x) { decoder.NewFrame -= current; decoder.NewFrame += x; current = x; }
原理说明:
- 每次切换处理逻辑时,先从事件中移除当前正在订阅的委托(保存在
current变量里)。 - 再将新的处理委托
x添加到事件中。 - 最后更新
current变量,记录当前订阅的委托,方便下次切换时移除。
优势:
- 性能高效:每次切换只做一次移除和一次添加操作,没有额外的中间调用开销,完全满足视频处理对实时性的要求。
- 可读性高:所有事件订阅的切换逻辑都集中在这个函数里,你在任何需要切换回
playingStarted的地方,只需要调用hookNewFrameEvent(playingStarted)即可,不用重复写-=和+=的代码,避免了代码冗余和出错的可能。
如果你需要初始化时就订阅playingStarted,只需要在构造函数里调用hookNewFrameEvent(playingStarted)就可以了,完全替代你最初的订阅代码。
内容的提问来源于stack exchange,提问作者Larry

