如何在自动 "locking" 使用 Window.PreviewMouseMoveEvent 的 (WPF) 应用程序时改进 CPU 的使用?

How to improve CPU usage when automatically "locking" a (WPF) application using Window.PreviewMouseMoveEvent?

目前我正在使用

 EventManager.RegisterClassHandler(typeof(Window),
                                   Window.PreviewMouseMoveEvent,
                                   new MouseEventHandler(OnPreviewMouseMove));

其中 OnPreviewMouseMove 仅在用于处理我的应用程序自动超时的计时器上调用 Stop()Start()(用户应在一段时间不活动后重新输入凭据)。

我注意到此解决方案使用了相当多的 CPU 功率(例如,当我将鼠标悬停在 window 上时,在 Core i7 上为 3-12%) ,所以我想知道是否有更好的方法来处理这个问题。我知道鼠标移动的抖动和相对较低的 CPU 使用率不会是一个真正的问题,但我愿意接受更好的方法来处理这个问题。

我也不确定这是否可以用于非 WPF 应用程序(我的猜测是在这种情况下我需要不同的事件),但这可能对另一个问题很重要。

使用 Windows API 调用 GetLastInputInfo 确定最后一次键盘按下或鼠标移动发生的时间。这与屏幕保护程序用来确定何时打开的计时器相同。

这是我在其他项目中使用过的包装器class。 Idle 事件在当前 SynchronizationContext 或线程池线程上运行(如果没有的话)。

/// <summary>
/// A timer that raises the <see cref="Idle"/> event when it detects the session 
/// </summary>
public sealed class SystemIdleTimer : IDisposable
{
    private readonly System.Threading.Timer _timer;
    private readonly SynchronizationContext _synchronizationContext;

    /// <summary>
    /// This event is rasied when the sysstem's idle time is greater than <see cref="MaxIdleTime"/>.
    /// This event is posted to the SynchronizationContext that the constructor was run under.
    /// </summary>
    public event EventHandler Idle;

    /// <summary>
    /// The amount of idle time that must pass before the <see cref="Idle"/> event is raised.
    /// </summary>
    public TimeSpan MaxIdleTime { get; set; }

    /// <summary>
    /// Is the user currently detected as idle;
    /// </summary>
    public bool IsDetectedIdle { get; private set; }

    /// <summary>
    /// Creates a new timer with a specified trigger level and a check frequency of once a minute.
    /// </summary>
    /// <param name="maxIdleTime">The amount of idle time that must pass before the <see cref="Idle"/> event is raised.</param>
    public SystemIdleTimer(TimeSpan maxIdleTime)
        : this(maxIdleTime, TimeSpan.FromMinutes(1))
    {
    }

    /// <summary>
    /// Creates a new timer with a specified trigger level and a check frequency.
    /// </summary>
    /// <param name="maxIdleTime">The amount of idle time that must pass before the <see cref="Idle"/> event is raised.</param>
    /// <param name="checkInterval">The frequency in miliseconds to check the idle timer.</param>
    public SystemIdleTimer(TimeSpan maxIdleTime, TimeSpan checkInterval)
    {
        MaxIdleTime = maxIdleTime;
        _synchronizationContext = SynchronizationContext.Current;
        _timer = new System.Threading.Timer(TimerCallback, null, checkInterval, checkInterval);
    }
    public void Dispose()
    {
        _timer.Dispose();
        Idle = null;
    }

    private void TimerCallback(object state)
    {
        var idleTime = GetIdleTime();
        if (idleTime > MaxIdleTime)
        {
            if (!IsDetectedIdle)
            {
                IsDetectedIdle = true;
                OnIdle();
            }
        }
        else
        {
            IsDetectedIdle = false;
        }
    }

    private void OnIdle()
    {
        var idle = Idle;
        if (idle != null)
        {
            if (_synchronizationContext != null)
            {
                _synchronizationContext.Post(state => idle(this, EventArgs.Empty), null);
            }
            else
            {
                idle(this, EventArgs.Empty);
            }
        }
    }

    /// <summary>
    /// Returns the amout of time the system has been idle.
    /// </summary>
    /// <returns>A TimeSpan representing the idle time for the session.</returns>
    public static TimeSpan GetIdleTime()
    {
        try
        {
            uint idleMiliseconds = 0;
            LASTINPUTINFO lastInputInfo = new LASTINPUTINFO();
            lastInputInfo.cbSize = (uint)Marshal.SizeOf(lastInputInfo);
            lastInputInfo.dwTime = 0;

            uint systemUpTime = GetTickCount();

            if (GetLastInputInfo(ref lastInputInfo))
            {
                uint lastInputTime = lastInputInfo.dwTime;

                if (lastInputTime > systemUpTime)
                {
                    // The elapsed time is stored as a DWORD value. Therefore, the time will wrap around to zero if the system is run continuously for 49.7 days.
                    // so, we need a bit more math...

                    // how far between last input and the current time rolling over to 0
                    idleMiliseconds = (uint.MaxValue - lastInputTime);

                    // add that to the current ticks
                    idleMiliseconds = idleMiliseconds + systemUpTime;
                }
                else
                {
                    idleMiliseconds = systemUpTime - lastInputTime;
                }
            }


            return TimeSpan.FromMilliseconds(idleMiliseconds);
        }
        catch (Exception)
        {
            return TimeSpan.Zero;
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct LASTINPUTINFO
    {
        [MarshalAs(UnmanagedType.U4)]
        public UInt32 cbSize;
        [MarshalAs(UnmanagedType.U4)]
        public UInt32 dwTime;
    }

    [DllImport("user32.dll")]
    static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);

    [DllImport("kernel32.dll")]
    static extern uint GetTickCount();
}