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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 10:34:51