【问题标题】:How can I avoid crashing when creating a precision timer with WinMM.dll?使用 WinMM.dll 创建精密计时器时如何避免崩溃?
【发布时间】:2021-12-01 01:20:00
【问题描述】:

我正在尝试创建一个精确计时器。我找到了一个使用 WinMM.dll 创建的示例。该示例工作得很好。但它与第一​​个垃圾收集器一起崩溃。

如何防止垃圾收集器阻塞计时器?

public class WinMMWrapper : IDisposable
{
    [DllImport("WinMM.dll", SetLastError = true)]
    public static extern uint timeSetEvent(int msDelay, int msResolution,
        TimerEventHandler handler, ref int userCtx, int eventType);

    [DllImport("Winmm.dll", CharSet = CharSet.Auto)]  // <=== ADD THIS
    static extern uint timeKillEvent(uint uTimerID);  // <=== ADD THIS

    public delegate void TimerEventHandler(uint id, uint msg, ref int userCtx,
        int rsv1, int rsv2);

    public enum TimerEventType
    {
        OneTime = 0,
        Repeating = 1,
    }

    private readonly Action _elapsedAction;
    private readonly int _elapsedMs;
    private readonly int _resolutionMs;
    private readonly TimerEventType _timerEventType;
    private uint _timerId;   // <=== ADD THIS
    private bool _disposed;   // <=== ADD THIS

    public WinMMWrapper(int elapsedMs, int resolutionMs, TimerEventType timerEventType, Action elapsedAction)
    {
        _elapsedMs = elapsedMs;
        _resolutionMs = resolutionMs;
        _timerEventType = timerEventType;
        _elapsedAction = elapsedAction;
    }

    public bool StartElapsedTimer()   // <=== RETURN bool
    {
        StopTimer(); // Stop any started timer

        int myData = 1;

        // === SET _timerId
        _timerId = timeSetEvent(_elapsedMs, _resolutionMs / 10, new TimerEventHandler(TickHandler), ref myData, (int)_timerEventType);
        return _timerId != 0;
    }

    public void StopTimer()  // <=== ADD THIS
    {
        if (_timerId != 0)
        {
            timeKillEvent(_timerId);
            _timerId = 0;
        }
    }

    private void TickHandler(uint id, uint msg, ref int userctx, int rsv1, int rsv2)
    {
        _elapsedAction();
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (!_disposed && disposing)
            StopTimer();

        _disposed = true;
    }

    ~WinMMWrapper()
    {
        Dispose(false);
    }
}    

我的静态类

public static class Global
{
    public static WinMMWrapper timer;
}

创建 WinMMWrapper

 private void TimerStart_Click(object sender, RoutedEventArgs e)
    {

        Global.timer = new WinMMWrapper(1, 1, WinMMWrapper.TimerEventType.Repeating, Tick);

        Global.timer.StartElapsedTimer();
    }

勾选功能

private static void Tick()
    {
        Console.WriteLine("Time : " + DateTime.Now.ToString("hh:mm:ss:ffff"));
    }

错误信息

Managed Debugging Assistant 'CallbackOnCollectedDelegate' : A callback was made on the garbage-collected delegate of type 'CanBusRandomDataGenerator!CanBusRandomDataGenerator.WinMMWrapper+TimerEventHandler::Invoke'. This can cause app crashes, corruption, and data loss. When delegating to unmanaged code, it must be kept alive by the managed application until it is guaranteed that the delegates will never be called.'

代码现在完全相同。它工作了大约 2 3 秒,然后它崩溃到以下错误。 WinMMWrapper 函数内发生错误,但未进入 Dispose。

【问题讨论】:

    标签: c# timer winmm


    【解决方案1】:
    1. 只要您使用计时器,就必须保持timer 变量处于活动状态。如果是局部变量,离开方法时会被GC回收。通过将此局部变量转换为类字段(可能是静态的)来实现。在控制台应用程序中,您仍然可以使用局部变量,但您必须添加 Console.ReadKey(); 以防止应用程序过早退出。

      此外,在此变量符合垃圾收集条件之前停止计时器。为此,让WinMMWrapper 实现IDisposable

    2. 确保回调 Action 所在的对象保持活动状态且未被释放!可能这就是您调用new WinMMWrapper(..., theAction) 的对象。

    public class WinMMWrapper : IDisposable
    {
        [DllImport("WinMM.dll", SetLastError = true)]
        public static extern uint timeSetEvent(int msDelay, int msResolution,
            TimerEventHandler handler, ref int userCtx, int eventType);
    
        [DllImport("Winmm.dll", CharSet = CharSet.Auto)]  // <=== ADD THIS
        static extern uint timeKillEvent(uint uTimerID);  // <=== ADD THIS
    
        public delegate void TimerEventHandler(uint id, uint msg, ref int userCtx,
            int rsv1, int rsv2);
    
        public enum TimerEventType
        {
            OneTime = 0,
            Repeating = 1,
        }
    
        private readonly Action _elapsedAction;
        private readonly int _elapsedMs;
        private readonly int _resolutionMs;
        private readonly TimerEventType _timerEventType;
        private iuint _timerId;   // <=== ADD THIS
        private bool _disposed;   // <=== ADD THIS
    
        public WinMMWrapper(int elapsedMs, int resolutionMs, TimerEventType timerEventType, Action elapsedAction)
        {
            _elapsedMs = elapsedMs;
            _resolutionMs = resolutionMs;
            _timerEventType = timerEventType;
            _elapsedAction = elapsedAction;
        }
    
        public bool StartElapsedTimer()   // <=== RETURN bool
        {
            Stop(); // Stop any started timer
    
            int myData = 1;
    
            // === SET _timerId
            _timerId = timeSetEvent(_elapsedMs, _resolutionMs / 10, new TimerEventHandler(TickHandler), ref myData, (int)_timerEventType);
            return _timerId != 0;
        }
    
        public void StopTimer()  // <=== ADD THIS
        {
            if (_timerId != 0)
            {
                timeKillEvent(_timerId);
                _timerId = 0;
            }
        }
    
        private void TickHandler(uint id, uint msg, ref int userctx, int rsv1, int rsv2)
        {
            _elapsedAction();
        }
    
        // === ADD Dispose and finalizer ===
    
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        private void Dispose(bool disposing)
        {
            if (!_disposed && disposing)
                StopTimer();
            }
            _disposed = true;
        }
    
        ~MMTimer()
        {
            Dispose(false);
        }
    }
    

    然后您可以在控制台应用程序中执行此操作:

    using (var timer = new WinMMWrapper(1, 1, WinMMWrapper.TimerEventType.Repeating,
        () => Console.WriteLine("Time : " + DateTime.Now.ToString("hh:mm:ss:fff"))) {
    
        Console.Writeline("Hit a key to stop the timer and quit the application!");
        Console.ReadKey();
    } // <= Here timer.Dispose() gets automatically called by using.
    

    如果由于计时器将在代码中的其他位置停止而无法使用 using 语句,您也可以显式调用 timer.Dispose();

    要使此代码线程安全,请将您的启动和停止计时器代码包含在 lock(this { ... } 语句中。

    【讨论】:

    • 感谢您的回答。我找不到 stop() 函数。我应该添加一个字段吗?
    • 这是一个错字,我称之为StopTimer。现在更正了。 (我没有测试过这段代码)
    • 我将定时器定义为全局静态变量,但问题没有解决。
    • 确保 Action 所在的对象也保持活动状态!这就是异常消息告诉您的内容。
    • 具有“Tick”功能的对象是“WinMMWrapper”类。我已将此类定义为静态的。不幸的是,我不明白这个对象是如何以及为什么会消失的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-03
    • 2018-02-02
    • 2020-06-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多