C#中取消订阅引用所属Camera对象的匿名ImageGrabbed事件处理方法的解决方案咨询
C#中取消订阅引用所属Camera对象的匿名ImageGrabbed事件处理方法的解决方案咨询
看起来你遇到的问题很典型——当用捕获外部变量的匿名方法订阅事件时,因为没法拿到这个委托的实例引用,导致后续无法取消订阅。结合你的代码场景,我给你几个可行的解决方案:
方案一:用字典存储相机与对应事件委托的关联(最直观易实现)
既然你需要给每个Camera单独管理它的ImageGrabbed事件处理委托,最直接的方式就是把委托实例保存下来,和Camera一一对应。
修改你的MyInterface类,添加一个字典来存储每个相机对应的事件处理委托:
public class MyInterface : IFrameGrabber { private List<Camera> _cameras = new List<Camera>(); // 添加字典存储相机和对应的事件委托 private Dictionary<Camera, EventHandler<ImageGrabbedEventArgs>> _cameraEventHandlers = new Dictionary<Camera, EventHandler<ImageGrabbedEventArgs>>(); public void AddCamera() { var cam = new Camera(); // 先创建委托实例,而不是直接用匿名方法订阅 var imageGrabbedHandler = new EventHandler<ImageGrabbedEventArgs>((_, e) => StreamGrabber_ImageGrabbed(cam, e)); cam.StreamGrabber.ImageGrabbed += imageGrabbedHandler; // 将委托和相机关联存入字典 _cameraEventHandlers.Add(cam, imageGrabbedHandler); cam.OnOpened += Camera_OnOpened; _cameras.Add(cam); } public void StopCamera(int id) // Just stopping not removing { var targetCam = _cameras[id]; // 从字典中取出对应的委托实例 if (_cameraEventHandlers.TryGetValue(targetCam, out var handler)) { // 取消订阅事件 targetCam.StreamGrabber.ImageGrabbed -= handler; // 如果之后还需要重新订阅,可以保留字典中的条目;如果确定不再需要,可以移除 // _cameraEventHandlers.Remove(targetCam); } } // 你的StreamGrabber_ImageGrabbed方法保持不变 public void StreamGrabber_ImageGrabbed(Camera sender, ImageGrabbedEventArgs e) { var timer = Stopwatch.StartNew(); var serialNumber = sender.CameraInfo["SerialNumber"]; var result = e.GrabResult; if (!result.GrabSucceeded) return; var stride = result.ComputeStride(); if (stride == null) return; _grabCount[serialNumber]++; var pixelType = e.GrabResult.PixelTypeValue switch { PixelType.Mono8 => MatType.CV_8UC1, _ => MatType.CV_8UC1, }; // TODO ImagesGrabbed?.Invoke(new(this, serialNumber, result.PixelDataPointer, result.Width, result.Height, (int)stride, pixelType, timer.ElapsedTicks / Stopwatch.Frequency)); } }
为什么这个方案可行?因为匿名方法每次声明都会生成一个全新的委托实例,直接写-=同一个匿名方法的代码其实是创建了另一个新实例,自然无法取消订阅。而我们把第一次订阅的那个委托实例保存下来,后续用同一个实例去取消订阅就能生效。
方案二:用ConditionalWeakTable关联相机与委托(避免潜在内存泄漏)
如果你担心字典会持有Camera的引用导致内存泄漏(比如当Camera从_cameras移除后,字典还存着它的引用),可以用System.Runtime.CompilerServices.ConditionalWeakTable来存储关联。它的特点是不会阻止GC回收Camera对象,当Camera被回收时,对应的委托条目也会被自动清理。
示例代码:
private ConditionalWeakTable<Camera, EventHandler<ImageGrabbedEventArgs>> _cameraEventHandlers = new ConditionalWeakTable<Camera, EventHandler<ImageGrabbedEventArgs>>(); // 在AddCamera里: var imageGrabbedHandler = new EventHandler<ImageGrabbedEventArgs>((_, e) => StreamGrabber_ImageGrabbed(cam, e)); cam.StreamGrabber.ImageGrabbed += imageGrabbedHandler; _cameraEventHandlers.Add(cam, imageGrabbedHandler); // 在StopCamera里: if (_cameraEventHandlers.TryGetValue(targetCam, out var handler)) { targetCam.StreamGrabber.ImageGrabbed -= handler; // ConditionalWeakTable没有公开的Remove方法,所以如果需要手动清理,只能等GC自动处理 }
这个方案适合不需要手动管理委托生命周期的场景,但它的缺点是没有公开的Remove方法,所以如果需要立即清理关联,还是方案一更灵活。
额外提醒
- 如果你之后需要重新启用相机的事件订阅,记得重新添加之前保存的委托实例,或者重新创建并更新字典里的关联。
- 确保在相机被彻底销毁前取消订阅所有事件,避免内存泄漏(第三方组件的事件很容易因为持有外部引用导致对象无法被回收)。
内容来源于stack exchange
相关产品推荐
相关产品推荐

