使用 WinMM.dll 创建精确计时器时如何避免崩溃?

How can I avoid crashing when creating a precision timer with WinMM.dll?

我正在尝试创建一个精确计时器。我找到了一个使用 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);
    }
}    

我的静态Class

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。

  1. 只要您使用计时器,就必须保持 timer 变量有效。如果是局部变量,离开方法时会被GC回收。通过将此局部变量转换为 class 字段(可能是静态的)来实现。在控制台应用程序中您仍然可以使用局部变量,但您必须添加一个 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 { ... } 语句中。