关于定时受“节能休眠或系统时钟调整”影响

使用Sleep、Timer、Task延时一定时间执行时会受到休眠等影响,比如:10点设置延时1小时后执行,10点半时计算机进入节能休眠状态,休眠持续20分钟,那么以上方法将会在11点20分执行(如果没有休眠影响正常是在11点执行)

解决上面问题可以调用API函数:CreateWaitableTimer、SetWaitableTimer来解决,此方法可设定相对时间或绝对时间来执行,不受中途休眠影响(执行时计算机已经处于工作状态),如果执行的时间点计算还处于休眠状态也可以唤醒计算机(需要电源配置支持),此情况会受到唤醒时间差的影响。

经本人测试,如果是设置了定时,中途时间变动不影响定时时效,比如:定时30分钟后执行,中途时间有变更,依然是30分钟后准时执行,采用SetWaitableTimer设置绝对时间(比如:12:00执行),中途系统时钟调整后,依然是12:00准时执行,相当于相对时间匹配时钟跳动计数(中途系统时间调快调慢,延时一样长),绝对时间匹配系统实时时间(中途系统时间调快了会,延时变短,调慢了,延时变长)。

相对时间 指一个时间差TimeSpan,如:多少分钟,多少秒;

绝对时间 指一个具体时间点DateTime,如:12:00。

下面是本人写的一个定时器类,可以模拟Thread.Sleep()延时一定时间执行且不受电源中途休眠影响,也可以实现定时到指定时间再执行, 同时也可以在其它线程调用CancelSleep()立即取消阻塞。

using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace WakeUpFromSleep
{
    /// <summary>
    /// 自定义的定时器(对象实体不建议跨线程使用,后面调用的Sleep会覆盖之前的调用,导致定时混乱)
    /// </summary>
    internal class MyTimer
    {
        /// <summary>
        /// 创建可等待计时器
        /// </summary>
        /// <param name="lpTimerAttributes">指定一个结构设置对象的安全特性,传递零值可使用对象的默认安全设置</param>
        /// <param name="bManualReset">TRUE,手动重置计时器;FALSE,自动重置计时器</param>
        /// <param name="lpTimerName">指定可等待计时器对象的名称(如果已经存在相同名称的计时器则反回原来已经创建的计时器)</param>
        /// <returns></returns>
        [DllImport("kernel32.dll", SetLastError = true)]        
        public static extern SafeWaitHandle CreateWaitableTimer(IntPtr lpTimerAttributes, bool bManualReset, string lpTimerName);

        /// <summary>
        /// 启动一个可等待计时器,将它设为未发信号状态
        /// </summary>
        /// <param name="hTimer">定时器</param>
        /// <param name="pDueTime">时间(正数为绝对时间/时间点,负数为相对时间/时间差)</param>
        /// <param name="lPeriod">周期间隔(如果是0只触发一次,否则重复触发)</param>
        /// <param name="pfnCompletionRoutine">回调函数</param>
        /// <param name="lpArgToCompletionRoutine">回调函数的参数</param>
        /// <param name="fResume">如果为TRUE,而且系统支持电源管理,那么在计时器触发的时候,系统会退出省电模式。</param>
        /// <returns></returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SetWaitableTimer(SafeWaitHandle hTimer, [In] ref long pDueTime, int lPeriod, IntPtr pfnCompletionRoutine, IntPtr lpArgToCompletionRoutine, bool fResume);
        

        /// <summary>
        /// 关闭句柄释放资源
        /// </summary>
        /// <param name="hObject"></param>
        /// <returns></returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool CloseHandle(SafeWaitHandle hObject);

        /// <summary>
        /// 取消计时器(调用后计时器将不会触发,关联Event将无法收到信号,调用WaitOne会一直阻塞)
        /// </summary>
        /// <param name="hTimer"></param>
        /// <returns></returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool CancelWaitableTimer(SafeWaitHandle hTimer);

        /// <summary>
        /// 等待计时器信号
        /// </summary>
        /// <param name="hTimer">计时器</param>
        /// <param name="dwMilliseconds">最大等待时长(毫秒)</param>
        /// <returns></returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern uint WaitForSingleObject(SafeWaitHandle hTimer, uint dwMilliseconds);
             

        EventWaitHandle eventHandle;
        SafeWaitHandle waitHandle;
        public MyTimer()
        {
            eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
            waitHandle = CreateWaitableTimer(IntPtr.Zero, true, DateTime.Now.Ticks.ToString());
            eventHandle.SafeWaitHandle = waitHandle;
        }
        /// <summary>
        /// 将当前线程挂起指定毫秒数(会覆盖之前调用的Sleep或SleepToDatetime)
        /// </summary>
        /// <param name="milliseconds">毫秒数</param>
        public void Sleep(int milliseconds)
        {
            long duetime = milliseconds;
            duetime = duetime * -10000;
            if (SetWaitableTimer(waitHandle, ref duetime, 0, IntPtr.Zero, IntPtr.Zero, true))
            {
                eventHandle.WaitOne();                
            }
            else
            {
                Thread.Sleep(milliseconds);
            }
        }
        /// <summary>
        /// 将当前线程挂起到指定时间(会覆盖之前调用的Sleep或SleepToDatetime)
        /// </summary>
        /// <param name="dateTime">时间(如果时间值小于当前时间,则立即返回)</param>
        public void SleepToDatetime(DateTime dateTime)
        {
            long duetime = dateTime.ToFileTime();
            if (SetWaitableTimer(waitHandle, ref duetime, 0, IntPtr.Zero, IntPtr.Zero, true))
            {
                eventHandle.WaitOne();
            }
            else
            {                
                while(DateTime.Now<dateTime)
                {
                    Thread.Sleep(1000);
                }
            }
        }
        /// <summary>
        /// 取消当前挂起,使Sleep或SleepToDatetime立即返回(多线程使用)
        /// </summary>
        /// <returns></returns>
        public bool CancelSleep()
        {
            long duetime = 0;
            return SetWaitableTimer(waitHandle, ref duetime, 0, IntPtr.Zero, IntPtr.Zero, true);
        }
        /// <summary>
        /// 释放资源(使用完记得要调用)
        /// </summary>
        public void Dispose()
        {
            eventHandle.Dispose();
            waitHandle.Dispose();
        }
        /// <summary>
        /// 一定时间后唤醒一次计算机(需要在电源配置中启用功能,睡眠-允许使用唤醒定时器-启用)
        /// 参考链接:https://www.cnblogs.com/wanglg/p/7509717.html
        /// </summary>
        /// <param name="second">多少秒后唤醒</param>
        /// <exception cref="Win32Exception"></exception>
        public static void SetWaitForWakeUpTime(long second)
        {
            long duetime = second * -10000000;
            using (SafeWaitHandle handle = CreateWaitableTimer(IntPtr.Zero, true, DateTime.Now.Ticks.ToString()))
            {
                if (SetWaitableTimer(handle, ref duetime, 0, IntPtr.Zero, IntPtr.Zero, true))
                {
                    using (EventWaitHandle wh = new EventWaitHandle(false, EventResetMode.AutoReset))
                    {
                        wh.SafeWaitHandle = handle;
                        //Application.SetSuspendState(PowerState.Suspend, true, false);//通知系统进入休眠状态
                        //wh.WaitOne();//等待定时器激发
                    }
                }
                else
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }
                //CloseHandle(handle);//不能在using块中使用,导致重复释放会抛异常
            }

        }
        /// <summary>
        /// 将当前线程挂起指定毫秒数
        /// 为避免重复创建定时器,建议先创建实例对象再调用,不要使用静态方法
        /// </summary>
        /// <param name="milliSecond">毫秒</param>
        /// <exception cref="Win32Exception"></exception>
        public static void Wait(long milliSecond)
        {
            long duetime = milliSecond * -10000;
            using (SafeWaitHandle handle = CreateWaitableTimer(IntPtr.Zero, true, DateTime.Now.Ticks.ToString()))
            {
                if (SetWaitableTimer(handle, ref duetime, 0, IntPtr.Zero, IntPtr.Zero, true))//最后一个参数代表同时唤醒休眠
                {
                    using (EventWaitHandle wh = new EventWaitHandle(false, EventResetMode.AutoReset))
                    {
                        wh.SafeWaitHandle = handle;
                        wh.WaitOne();//等待定时器激发
                    }
                }
                else
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }
            }

        }
        /// <summary>
        /// 将当前线程挂起到指定时间
        /// 为避免重复创建定时器,建议先创建实例对象再调用,不要使用静态方法
        /// </summary>
        /// <param name="dateTime">时间(如果时间值小于当前时间,则立即返回)</param>
        /// <exception cref="Win32Exception"></exception>
        public static void WaitToDatetime(DateTime dateTime)
        {
            long duetime = dateTime.ToFileTime();
            using (SafeWaitHandle handle = CreateWaitableTimer(IntPtr.Zero, true, DateTime.Now.Ticks.ToString()))
            {
                if (SetWaitableTimer(handle, ref duetime, 0, IntPtr.Zero, IntPtr.Zero, true))//最后一个参数代表同时唤醒休眠
                {
                    using (EventWaitHandle wh = new EventWaitHandle(false, EventResetMode.AutoReset))
                    {
                        wh.SafeWaitHandle = handle;
                        wh.WaitOne();//等待定时器激发
                    }
                }
                else
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }
            }

        }

    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lzl_li

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值