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

系统休眠检测回调委托被垃圾回收致应用崩溃求助

问题:系统休眠检测代码导致应用崩溃(CallbackOnCollectedDelegate错误)

我实现了一段检测系统休眠并保存时间戳的代码,但系统恢复后应用偶尔会崩溃,报错如下:

"Managed Debugging Assistant 'CallbackOnCollectedDelegate' :
'A callback was made on a garbage collected delegate of type 'Tray_App.Classes.SleepDetect+DeviceNotifyCallbackRoutine::Invoke'.
This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until
it is guaranteed that they will never be called.'"

我知道是对象被回收后仍被引用,但不知道怎么防止回收,之前没用过委托。以下是我的实现类代码:

internal class SleepDetect
{
    public SleepDetect()
    {
        //Register event to handle sleep detection
        IntPtr registrationHandle = new IntPtr();
        DeviceNotifySubscribeParameters recipient = new DeviceNotifySubscribeParameters
        {
            Callback = DeviceNotifyCallback,
            Context = IntPtr.Zero
        };
        IntPtr pRecipient = Marshal.AllocHGlobal(Marshal.SizeOf(recipient));
        Marshal.StructureToPtr(recipient, pRecipient, false);
        var result = PowerRegisterSuspendResumeNotification(DeviceNotifyCallbackVal, ref recipient, ref registrationHandle);
        if (result != 0)
            MessageBox.Show(@"Error registering for power notifications: " + Marshal.GetLastWin32Error(), @"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }

    //Sleep detect implementation
    [DllImport("Powrprof.dll", SetLastError = true)]
    static extern uint PowerRegisterSuspendResumeNotification(uint flags, ref DeviceNotifySubscribeParameters recipient, ref IntPtr registrationHandle);
    private const int PbtApmresumeautomatic = 18; // (0x12) - Operation is resuming automatically from a low-power state.This message is sent every time the system resumes.
    private const int PbtApmresumesuspend = 7; // (0x7) - Operation is resuming from a low-power state.This message is sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered by user input, such as pressing a key.
    private const int PbtApmsuspend = 4; // (0x4) - System is suspending operation.
    private const int DeviceNotifyCallbackVal = 2;

    //OS callback delegate definition
    delegate int DeviceNotifyCallbackRoutine(IntPtr context, int type, IntPtr setting);

    //A callback definition
    [StructLayout(LayoutKind.Sequential)]
    struct DeviceNotifySubscribeParameters
    {
        public DeviceNotifyCallbackRoutine Callback;
        public IntPtr Context;
    }

    //Power change event for sleep detection
    private static int DeviceNotifyCallback(IntPtr context, int type, IntPtr setting)
    {
        switch (type)
        {
            case PbtApmsuspend:
                DateTime cur = DateTime.Now;
                Properties.Settings.Default.ShutdownAtDate = cur;
                Properties.Settings.Default.Save();
                break;
            case PbtApmresumeautomatic:
                break;
            case PbtApmresumesuspend:
                break;
        }
        return 0;
    }
}

解决方法

问题核心是传给非托管代码的委托被GC(垃圾回收器)回收了。非托管系统无法感知托管的GC机制,仍会在系统休眠/恢复时调用已被回收的委托,直接导致崩溃。要解决这个问题,必须把委托实例保留在托管代码的根引用中,避免被GC回收。

具体修改步骤:

  1. 在SleepDetect类中添加类级别的字段,专门保存委托实例,不让GC回收它:
    private DeviceNotifyCallbackRoutine _keepAliveCallback; // 保留委托引用,防止被GC回收
    private IntPtr _registrationHandle; // 保存注册句柄,用于后续取消注册
    
  2. 在构造函数中,先实例化委托并赋值给上述字段,再把字段传给DeviceNotifySubscribeParameters:
    public SleepDetect()
    {
        _registrationHandle = new IntPtr();
        // 先实例化委托并保存到类字段
        _keepAliveCallback = DeviceNotifyCallback;
        DeviceNotifySubscribeParameters recipient = new DeviceNotifySubscribeParameters
        {
            Callback = _keepAliveCallback, // 使用类字段的委托实例
            Context = IntPtr.Zero
        };
        IntPtr pRecipient = Marshal.AllocHGlobal(Marshal.SizeOf(recipient));
        Marshal.StructureToPtr(recipient, pRecipient, false);
        var result = PowerRegisterSuspendResumeNotification(DeviceNotifyCallbackVal, ref recipient, ref _registrationHandle);
        if (result != 0)
            MessageBox.Show(@"Error registering for power notifications: " + Marshal.GetLastWin32Error(), @"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        
        Marshal.FreeHGlobal(pRecipient); // 释放分配的非托管内存,避免泄漏
    }
    
  3. 新增取消注册的方法,在应用关闭时调用,同时允许GC回收委托:
    public void Unregister()
    {
        if (_registrationHandle != IntPtr.Zero)
        {
            PowerUnregisterSuspendResumeNotification(_registrationHandle);
            _registrationHandle = IntPtr.Zero;
            _keepAliveCallback = null; // 解除引用,允许GC回收委托
        }
    }
    
  4. 补充取消注册的API声明:
    [DllImport("Powrprof.dll", SetLastError = true)]
    static extern uint PowerUnregisterSuspendResumeNotification(IntPtr registrationHandle);
    

修改后的完整类代码:

internal class SleepDetect
{
    private DeviceNotifyCallbackRoutine _keepAliveCallback; // 保留委托引用,防止GC回收
    private IntPtr _registrationHandle; // 保存注册句柄,用于取消注册

    public SleepDetect()
    {
        //Register event to handle sleep detection
        _registrationHandle = new IntPtr();
        // 实例化委托并保存到类字段
        _keepAliveCallback = DeviceNotifyCallback;
        DeviceNotifySubscribeParameters recipient = new DeviceNotifySubscribeParameters
        {
            Callback = _keepAliveCallback,
            Context = IntPtr.Zero
        };
        IntPtr pRecipient = Marshal.AllocHGlobal(Marshal.SizeOf(recipient));
        Marshal.StructureToPtr(recipient, pRecipient, false);
        var result = PowerRegisterSuspendResumeNotification(DeviceNotifyCallbackVal, ref recipient, ref _registrationHandle);
        if (result != 0)
            MessageBox.Show(@"Error registering for power notifications: " + Marshal.GetLastWin32Error(), @"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        
        Marshal.FreeHGlobal(pRecipient); // 释放非托管内存
    }

    // 取消电源通知注册,应用关闭时调用
    public void Unregister()
    {
        if (_registrationHandle != IntPtr.Zero)
        {
            PowerUnregisterSuspendResumeNotification(_registrationHandle);
            _registrationHandle = IntPtr.Zero;
            _keepAliveCallback = null; // 允许GC回收委托
        }
    }

    //Sleep detect implementation
    [DllImport("Powrprof.dll", SetLastError = true)]
    static extern uint PowerRegisterSuspendResumeNotification(uint flags, ref DeviceNotifySubscribeParameters recipient, ref IntPtr registrationHandle);
    
    // 取消注册的API
    [DllImport("Powrprof.dll", SetLastError = true)]
    static extern uint PowerUnregisterSuspendResumeNotification(IntPtr registrationHandle);

    private const int PbtApmresumeautomatic = 18; // (0x12) - Operation is resuming automatically from a low-power state.This message is sent every time the system resumes.
    private const int PbtApmresumesuspend = 7; // (0x7) - Operation is resuming from a low-power state.This message is sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered by user input, such as pressing a key.
    private const int PbtApmsuspend = 4; // (0x4) - System is suspending operation.
    private const int DeviceNotifyCallbackVal = 2;

    //OS callback delegate definition
    delegate int DeviceNotifyCallbackRoutine(IntPtr context, int type, IntPtr setting);

    //A callback definition
    [StructLayout(LayoutKind.Sequential)]
    struct DeviceNotifySubscribeParameters
    {
        public DeviceNotifyCallbackRoutine Callback;
        public IntPtr Context;
    }

    //Power change event for sleep detection
    private static int DeviceNotifyCallback(IntPtr context, int type, IntPtr setting)
    {
        switch (type)
        {
            case PbtApmsuspend:
                DateTime cur = DateTime.Now;
                Properties.Settings.Default.ShutdownAtDate = cur;
                Properties.Settings.Default.Save();
                break;
            case PbtApmresumeautomatic:
                break;
            case PbtApmresumesuspend:
                break;
        }
        return 0;
    }
}

额外提示:

  • 构造函数中使用Marshal.AllocHGlobal分配的非托管内存必须释放,否则会造成内存泄漏,修改后的代码已补充该操作。
  • 应用关闭时,务必调用SleepDetect实例的Unregister方法,完成电源通知的取消注册,避免资源泄漏。

内容的提问来源于stack exchange,提问作者Shazy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 12:47:49